聊聊非關係型數據庫MongoDB索引

mongodb
今天和你們簡單聊下Mongo數據庫的索引。

Mongo索引是基於B-tree,存儲在一個易於遍歷讀取的數據集合中,它是對數據庫表中一列或多列的值進行排序的一種結構。mongodb

數據庫的索引和咱們書籍目錄類似,有了索引,咱們不須要翻閱整本書,只須要查看目錄就知道咱們要的內容在哪兒,而且直接定位到,這種方式能大大提升咱們的查找效率。數據庫

聚個例子

爲了讓你們更直觀瞭解,我基於mongo3.6簡單插入了1百萬條數據進去,經過explain來進行分析查詢狀況。數組

scanv_rs:PRIMARY> db.users.count()
1000000
scanv_rs:PRIMARY> db.users.ensureIndex({"username": 1})

建立索引以後
scanv_rs:PRIMARY> db.users.find({"username": 'user10001'}).explain()


{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "test.users",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "username" : {
                "$eq" : "user10001"
            }
        },
        "winningPlan" : {
            "stage" : "FETCH",  # 經過返回index位置檢索文檔
            "inputStage" : {
                "stage" : "IXSCAN", # 索引查找,沒有創建索引就是COLLSCAN
                "keyPattern" : {
                    "username" : 1
                },
                "indexName" : "username_1", # 索引名字
                "isMultiKey" : false,  # 創建在數組上,這兒是true
                "multiKeyPaths" : {
                    "username" : [ ]
                },
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 2,
                "direction" : "forward",
                "indexBounds" : {
                    "username" : [
                        "[\"user10001\", \"user10001\"]"
                    ]
                }
            }
        },
        "rejectedPlans" : [ ]
    },
    }
}

有部分刪減
scanv_rs:PRIMARY>db.users.find({"username":'user10001'}).explain('executionStats')

複製代碼

這裏就不進一步展現了。 上面最後語句執行的結果這裏就不展現了,結果就是在1000000條數據,"executionTimeMillis" 字段值創建前花費1450ms,創建後花費2ms, 相差百倍,totalDocsExamined 一個創建索引前全文掃描1000000條,創建後只有1條。安全

你們有興趣能夠自行對比一下。從上面咱們能夠看到索引的威力。bash

索引有哪幾種?

簡單說完索引以後,咱們再來聊下索引的分類,索引主要分爲:惟一索引和稀疏索引。服務器

惟一索引能夠確保集合的每個文檔的指定健都有惟一值。性能

舉個例子,咱們要在集合裏面創建username索引,經過這種方式能夠確保username在不一樣的文檔裏面擁有的username是惟一的(其實咱們經常使用_id索引也是惟一索引)測試

db.yourcollection.ensureIndex({"username":1}, {"unique": true})spa

若是在上面的集合中添加相同username數據就會致使失敗 E11000 dumplicate key error…設計

咱們常常在集合上建立索引的時候會碰到上面那個錯誤,緣由就是咱們集合裏面已經有了重複的數據。

碰到這種狀況,一般的方式是

先找出重複的數據進行清理掉,再重建(線上),好比經過聚合

使用dropDups簡單粗暴處理

經過dropDups的方式,在建立索引的時候加上,能夠強制性創建惟一索引,遇到重複的值,第一個保留,其餘進行刪掉。

db.yourcollection.ensureIndex({"username":1}, {"unique": true, "dropDups": true})

第二種方式一般用在開發測試環境中,線上環境請注意。

說完惟一索引,咱們再來了解下稀疏索引。

因爲惟一索引會把null看作值,因此沒法將多個缺乏惟一索引中的健的文檔插入到集合中。

這個時候咱們能夠經過建立稀疏索引的方式來進行,一個值可存在可不存在,若是存在就必須是惟一的。咱們只須要添加一個spare選項就能建立稀疏索引。

好比咱們要創建一個可選的姓名,若是提供了姓名,那麼它的值必須是惟一的。

db.yourcollection.ensureIndex({'username': 1}, {'unique': true, 'sparse': true})

上面是單一健索引,其實咱們還有基於多個健的複合索引,全文索引,地理空間索引,因爲篇幅有限,這裏面咱們就先不深刻進去。

怎麼創建索引?

介紹分類以後,咱們聊聊怎麼創建索引,新建索引是一件費時費資源的事情,默認狀況索引建立會阻塞對數據庫的讀寫請求,一直到索引建立完成。

若是但願建立因此任然能處理讀寫請求,建立時咱們須要指定background參數。

好比在單機服務器上咱們能夠加上background 爲True。

db.yourcollection.ensureIndex({'username': 1}, {background: true})

這種方式雖然會消耗比較長的時間,可是不會鎖定數據庫,從而保證其餘操做的運行。

一樣在數據量小的集合的副本級上面咱們也能這樣作,在主節點上創建索引,而後同步到備份節點上面。

可是在數據量大的集合咱們須要拆分每一個節點來進行創建索引,避免索引期間全部副本級沒法正常工做,致使出現問題。

拆分從節點創建索引步驟以下:

  1. 關閉一個從節點A,獨立啓動

  2. 在這個從節點A創建索引

  3. 從新將A加入副本級

  4. 重複上面三個步奏

對於主節點咱們能夠進行故障轉移爲從節點或者直接進行創建索引(對性能有必定影響),經過上面的方式就能大大提升咱們創建索引安全穩定性。

我曾經就碰到過有同窗沒有拆分執行就創建索引的狀況,致使幾臺DB節點打滿,沒法工做,你們須要注意下,若是因爲環境因素作不到,那麼咱們須要找DB空閒時間進行上述操做。

什麼時候用索引?

雖然絕大多數場景,咱們都必需要有索引才能提升效率。

但有時候咱們須要考慮是否真的有必要使用索引,由於使用索引須要進行兩次查找,一次查找索引條目,一次根據索引指針查找相應的文檔,而全表掃描只須要一次查找過程。

下面咱們來對比一下,索引適用與不適用狀況。

從上面圖咱們知道索引適合,集合大,文檔大,選擇性查詢狀況,不適合與之相反的集合小,文檔小,非選擇性查詢的狀況。

幾點建議

關於索引的一些建議:

  1. 學會使用explain進行分析,對比索引和非索引區別,檢索條數,消耗毫秒數等

  2. 關注讀寫比率,由於若是應用寫多讀少 ,添加索引會影響寫入性能

  3. 在索引基數高的地方創建索引(好比郵箱,用戶名,而不是性別)

  4. $or 查詢是兩次獨立查詢拼接而成,效率沒有使用 $IN的高

  5. $ne 或者 $nin 操做在索引上是無效的

  6. 設計多個字段索引時,先用精確匹配查詢,而後再用範圍匹配(好比y>10&&y<100)的字段


相關文章
相關標籤/搜索