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})
這種方式雖然會消耗比較長的時間,但是不會鎖定資料庫,從而保證其他操作的執行。
同樣在資料量小的集合的副本級上面我們也能這樣做,在主節點上建立索引,然後同步到備份節點上面。
但是在資料量大的集合我們需要拆分每個節點來進行建立索引,避免索引期間所有副本級無法正常工作,導致出現問題。
拆分從節點建立索引步驟如下:
-
關閉一個從節點A,獨立啟動
-
在這個從節點A建立索引
-
重新將A加入副本級
-
重複上面三個步奏
對於主節點我們可以進行故障轉移為從節點或者直接進行建立索引(對效能有一定影響),通過上面的方式就能大大提高我們建立索引安全穩定性。
我曾經就碰到過有同學沒有拆分執行就建立索引的情況,導致幾臺DB節點打滿,無法工作,大家需要注意下,如果由於環境因素做不到,那麼我們需要找DB空閒時間進行上述操作。
何時用索引?
雖然絕大多數場景,我們都必須要有索引才能提高效率。
但有時候我們需要考慮是否真的有必要使用索引,因為使用索引需要進行兩次查詢,一次查詢索引條目,一次根據索引指標查詢相應的文件,而全表掃描只需要一次查詢過程。
下面我們來對比一下,索引適用與不適用情況。
從上面圖我們知道索引適合,集合大,文件大,選擇性查詢情況,不適合與之相反的集合小,文件小,非選擇性查詢的情況。
幾點建議
關於索引的一些建議:
-
學會使用explain進行分析,對比索引和非索引區別,檢索條數,消耗毫秒數等
-
關注讀寫比率,因為如果應用寫多讀少 ,新增索引會影響寫入效能
-
在索引基數高的地方建立索引(比如郵箱,使用者名稱,而不是性別)
-
$or 查詢是兩次獨立查詢拼接而成,效率沒有使用 $IN的高
-
$ne 或者 $nin 操作在索引上是無效的
-
設計多個欄位索引時,先用精確匹配查詢,然後再用範圍匹配(比如y>10&&y<100)的欄位