寫在前面sql
關於什麼是索引以及惟一索引這裏就不作說明了,不清楚的能夠自行谷歌或者百度。是什麼引發我寫這篇文章呢,這來自於以前項目中的一個問題。shell
咱們用的是MongoDB數據存儲用戶信息,用戶表中曾經用戶註冊是經過手機號註冊的,因此很理所固然的給手機號加上了惟一索引(Unique),這是沒有什麼毛病。後期,咱們需求改了。你也能夠想到變成了既能夠手機號註冊又能夠郵箱註冊,這個時候因爲手機號加了Unique索引,事實上這時候是會出現問題的。數據庫
func init() { phoneIndex := mgo.Index{ Key: []string{"phone"}, Unique: true, } col := db.Collection(&User{}) col.EnsureIndex(phoneIndex) }
固然這問題其實也容易想到,當用戶經過郵箱註冊此時手機號填空的時候,第一次沒什麼問題,下個用戶再以這種方式註冊的時候便會提示創建在phone上的索引值重複,很正常嘛,由於插入了兩個空值,注意這裏是空字符串,而不是null。數據結構
因而咱們嘗試修改,因爲MongoDB是文檔型靈活的數據庫,少插多插一兩個字段不受影響,因此咱們嘗試修改User實體Phone字段的入口,當phone是空字符串的時候,不讓插入此字段。因而,咱們便在phone字段中加入了omitempty標籤(咱們微服務用Go語言寫的)。下面展現User一部份內容:微服務
type User struct { Email string `bson:"email"` Salt string `bson:"salt"` Phone string `bson:"phone,omitempty"` IDCard string `bson:"idcard"` RealName string `bson:"realname"` AuthStatus int `bson:"auth_status"` }
能夠看到phone字段後加了omitempty標籤,表示當該字段爲空的時候不插入。這仍是會出現問題,那麼既然仍是會出問題爲何會想到這麼解決呢?這源於對Mysql的使用經驗,習慣性的覺得MongoDB和Mysql那樣,對null的值會不作其索引。也就是說,在Mysql中,若在多條記錄中Phone值爲Null是被容許的。性能
上面那種作法,仍是會報錯,提示插入了重複的值,只不過這時不是空字符串,而是null。因此有時候就不要把Mysql那套拿來了,Mysql是能夠的,但Mongo不行。mongo仍是會對該條記錄索引,即便該字段爲被插入。ui
我喜歡看官方文檔,下面給出MongoDB官方文檔說明:this
If a document does not have a value for the indexed field in a unique
index, the index will store a null value for this document. Because of
the unique constraint, MongoDB will only permit one document that
lacks the indexed field. If there is more than one document without a
value for the indexed field or is missing the indexed field, the index
build will fail with a duplicate key error.
其實已經說得很清楚了,稍微會點英語應該都能看懂,下面仍是給出翻譯版:spa
若是文檔沒有惟一索引中索引字段的值,則索引將爲此文檔存儲null值。因爲惟一約束,MongoDB只容許一個缺乏索引字段的文檔。若是有多個文檔沒有索引字段的值或缺乏索引字段,則索引構建將失敗並出現重複鍵錯誤。
也就是說這個字段哪怕在文檔中沒有,那麼該字段將會存null值,該字段上也不能同時出現兩個null值,這就是爲何上面那種作法仍是行不通的緣由,其實上面那種作法也打破了數據結構,雖然手機號未填,但數據庫中也不該該缺乏這個字段,儘管是非關係數據庫,畢竟還得考慮下業務設計。翻譯
解決方式
是否是就沒有解決方式了呢?固然有,Mongo提供了Sparse Index,被翻譯爲稀疏索引。下面是建立稀疏索引的例子:
db.getCollection("test").createIndex( { "phone": 1 }, { sparse: true })
執行上面的語句後,不會去索引不存在phone字段的文檔。也就是說存在纔對其索引,那麼此時和Unique索引結合起來就能夠派上用場了。Unqiue是惟一,Sparse是存在才索引。因此,當phone或email爲空的時候咱們能夠不將其插入這是能夠實現的。
db.getCollection("test").createIndex( { "phone": 1 }, { sparse: true,unique: true } )
上面是是mongo shell語法,一般咱們通常經過代碼中創建索引,修改以下(固然User結構體中Phone字段omitempty標籤仍是要有的):
func init() { phoneIndex := mgo.Index{ Key: []string{"phone"}, Unique: true, Sparse: true, } col := db.Collection(&User{}) col.EnsureIndex(phoneIndex) }
可是這又正如咱們前面說的那樣,打破了數據原有的數據結構。哎,有得有得。固然咱們還能夠從業務層面去解決,好比註冊時對其查詢等操做,固然會耗必定性能,無論你是那空間換時間,仍是拿時間換空間總得付出一個,別作一個太貪心的人。