聊聊非關係型資料庫MongoDB索引

濤哥聊Python發表於2018-10-31

mongodb
今天和大家簡單聊下Mongo資料庫的索引。

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

資料庫的索引和我們書籍目錄相似,有了索引,我們不需要翻閱整本書,只需要檢視目錄就知道我們要的內容在哪兒,並且直接定位到,這種方式能大大提高我們的查詢效率。

聚個例子

為了讓大家更直觀瞭解,我基於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條。

大家有興趣可以自行對比一下。從上面我們可以看到索引的威力。

索引有哪幾種?

簡單說完索引之後,我們再來聊下索引的分類,索引主要分為:唯一索引和稀疏索引。

唯一索引可以確保集合的每一個文件的指定健都有唯一值。

舉個例子,我們要在集合裡面建立username索引,通過這種方式可以確保username在不同的文件裡面擁有的username是唯一的(其實我們常用_id索引也是唯一索引)

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

如果在上面的集合中新增相同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空閒時間進行上述操作。

何時用索引?

雖然絕大多數場景,我們都必須要有索引才能提高效率。

但有時候我們需要考慮是否真的有必要使用索引,因為使用索引需要進行兩次查詢,一次查詢索引條目,一次根據索引指標查詢相應的文件,而全表掃描只需要一次查詢過程。

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

聊聊非關係型資料庫MongoDB索引

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

幾點建議

關於索引的一些建議:

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

  2. 關注讀寫比率,因為如果應用寫多讀少 ,新增索引會影響寫入效能

  3. 在索引基數高的地方建立索引(比如郵箱,使用者名稱,而不是性別)

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

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

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


聊聊非關係型資料庫MongoDB索引

聊聊非關係型資料庫MongoDB索引

相關文章