關於MongoDB惟一索引(Unique)的那些事

寫在前面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)
}

可是這又正如咱們前面說的那樣,打破了數據原有的數據結構。哎,有得有得。固然咱們還能夠從業務層面去解決,好比註冊時對其查詢等操做,固然會耗必定性能,無論你是那空間換時間,仍是拿時間換空間總得付出一個,別作一個太貪心的人。

相關文章
相關標籤/搜索