mongodb索引初探

基礎理論知識

索引作用

索引主要用於在mongodb數據庫中執行高效查找,在沒有索引的情況下在mongodb中查找數據需要遍歷整個collection(相當於mysql的表)的每條document(相當於mysql表中的一行)來匹配所要查詢的文檔,索引可以限制查詢時必須檢查的文檔數目。

索引的介紹

mongodb的索引 B-tree的數據結構進行存儲,這樣易於遍歷的形式存儲收藏集數據集的一小部分。
索引存儲一個特定的或者一組字段的值,按照該字段的值排序。
索引條目的排序支持有效的相等匹配和基於範圍的查詢操作。 另外,MongoDB可以使用索引中的順序返回排序的結果。

索引的類別

單字段索引

1.mongodb針對每個collection的_id都會建立唯一索引,在分片集羣中一定要保持_id的唯一性防止出錯。
2.在單字段索引中索引鍵的排序順序無關緊要,因爲mongob可以按照任意一種方式進行排序

複合索引

1.複合索引的順序
創建複合索引以支持對多個字段進行排序。
您可以在索引的所有鍵或子集上指定排序; 但是,排序鍵必須按照它們在索引中出現的順序列出。 例如,索引鍵樣式{a:1,b:1}可以支持{a:1,b:1}上的排序,但不能支持{b:1,a:1}上的排序。
爲了使查詢使用複合索引進行排序,cursor.sort()文檔中所有鍵的指定排序方向必須與索引鍵模式匹配或與索引鍵模式的反方向匹配。 例如,索引鍵模式{a:1,b:-1}可以支持{a:1,b:-1}和{a:-1,b:1}上的排序,但不能支持{a:- 1,b:-1}或{a:1,b:1}。
2. 複合索引的前綴
如果排序鍵對應於索引鍵或索引前綴,則MongoDB可以使用索引對查詢結果進行排序。 複合索引的前綴是在索引鍵模式的開頭由一個或多個鍵組成的子集。
例如,在數據集合上創建一個複合索引:

db.data.createIndex( { a:1, b: 1, c: 1, d: 1 } )

以下都能匹配到該索引:

{ a: 1 }
{ a: 1, b: 1 }
{ a: 1, b: 1, c: 1 }

Example Index Prefix
db.data.find().sort( { a: 1 } ) { a: 1 }
db.data.find().sort( { a: -1 } ) { a: 1 }
db.data.find().sort( { a: 1, b: 1 } ) { a: 1, b: 1 }
db.data.find().sort( { a: -1, b: -1 } ) { a: 1, b: 1 }
db.data.find().sort( { a: 1, b: 1, c: 1 } ) { a: 1, b: 1, c: 1 }
db.data.find( { a: { $gt: 4 } } ).sort( { a: 1, b: 1 } ) { a: 1, b: 1 }
當通過索引來進行查詢和排序的的時候,這些操作不需要在內存中對結果集在進行排序。

3.索引的排序和非前綴子集
索引可以支持對索引鍵模式的非前綴子集進行排序操作。 爲此,查詢必須在排序鍵之前的所有前綴鍵上包含相等條件。
例子:
集合有如下索引

{ a: 1, b: 1, c: 1, d: 1 }

以下操作可以使用索引來獲取排序順序:
Example Index Prefix
db.data.find( { a: 5 } ).sort( { b: 1, c: 1 } ) { a: 1 , b: 1, c: 1 }
db.data.find( { b: 3, a: 4 } ).sort( { c: 1 } ) { a: 1, b: 1, c: 1 }
db.data.find( { a: 5, b: { $lt: 3} } ).sort( { b: 1 } ) { a: 1, b: 1 }

查詢分析

可以使用

db.collection.find("條件").explain("executionStats")

來分析一條語句執行所使用的查詢計劃(是否有用到索引,用到了哪個索引)。
使用之後結果如下

{
   "queryPlanner" : {
         "plannerVersion" : 1,
         ...
         "winningPlan" : {
               "stage" : "FETCH",
               "inputStage" : {
                  "stage" : "IXSCAN",
                  "keyPattern" : {
                     "quantity" : 1
                  },
                  ...
               }
         },
         "rejectedPlans" : [ ]
   },
   "executionStats" : {
         "executionSuccess" : true,
         "nReturned" : 3,
         "executionTimeMillis" : 0,
         "totalKeysExamined" : 3,
         "totalDocsExamined" : 3,
         "executionStages" : {
            ...
         },
         ...
   },
   ...
}
涵蓋的意思如下
queryPlanner.winningPlan.inputStage.stage顯示IXSCAN以指示索引的使用。
executionStats.nReturned顯示3,指示查詢匹配並返回三個文檔。
executionStats.totalKeysExamined顯示3,表示MongoDB掃描了三個索引條目。 檢查的鍵數與返回的文檔數匹配,這意味着mongod僅需檢查索引鍵即可返回結果。 mongod不必掃描所有文檔,只需要將三個匹配的文檔拉入內存即可。 這導致非常有效的查詢。
executeStats.totalDocsExamined顯示3指示MongoDB掃描了三個文檔。

winningplan表示的是mongodb計劃選擇使用的索引(也可能不走索引入,collscan就是指全表掃描)
executionStates表示用winningplan執行查詢的實際統計信息。
nReturned:表示符合查詢(find)條件的文檔數。
executionTimeMillis:選擇查詢計劃和執行查詢所需要的總時間。
totalKeysExamined:掃描索引的條目數
totalDocsExamined :查詢期間執行檢查的文檔數。(ps:注意此處返回的數字是我們檢索的文檔總數,結果有時還需再與查詢條件進行對比篩選後再返回,如索引獲取800條,但是根據條件我們還需要篩選一部分出去,最後獲取到的與條件匹配可能只有100條,兩方差值越小代表索引效率越高。)
更詳細的說明可參考:https://docs.mongodb.com/manual/reference/explain-results/#explain.executionStats.nReturned
有關stage常見解釋

COLLSCAN進行集合掃描
IXSCAN用於掃描索引鍵
FETCH用於檢索文檔
SHARD_MERGE用於合併分片的結果
SHARDING_FILTER用於從分片中篩選出孤立文檔
SORT:表明在內存中進行了排序

實際場景分析

數據庫獲取數據的過程

從以上理論知識,可以得出mongodb數據庫獲取數據的過程:
1.檢查索引,選擇最優化的索引(所以索引不是越多越好,建立太多的索引也會影響查詢的性能)
2.根據索引(如果沒有索引就是全表查詢)獲取所有匹配上索引的數據(若有排序操作,且排序也匹配上了索引,則可以在這一步直接排序)
3.拿到索引獲取的數據集,再根據其他的查找條件對數據集再一次的進行過濾(所以索引的分辨度很重要)
4.若沒有根據索引排序,再將最終結果在內存中進行一次排序

謹慎掉坑

1.執行查詢及排序,索引使用了排序的索引,雖然排序使用了索引,但是由於查找條件沒有匹配上索引,所以導致了全表查詢

{ b: 1, c: 1, d: 1 }
存在索引b,c,d,執行以下查詢
Example	                                                         Index 
db.data.find( { a: 5 } ).sort( { b: 1, c: 1 } )          	{  b: 1, c: 1,d:1 }

這種情況會顯示匹配了索引,但是由於在尋找數據集的階段沒有匹配索引,所以依舊是全表查詢。
這種情況的理想的索引是{ a: 1, b: 1,c:1 }

2.謹慎使用$or

在mongodb中$or語句必須保證他的每一個條件都是索引,不然就會導致全表掃描

db.inventory.find( { $or: [ { quantity: { $lt: 20 } }, { price: 10 } ] } )

爲了支持此查詢不走全表掃描,你必須在quantity和price上分別建立索引

db.inventory.createIndex( { quantity: 1 } )
db.inventory.createIndex( { price: 1 } )

3.查詢必須在排序鍵之前的所有前綴鍵上包含相等條件

針對數據 abc表,有四個字段,a,b,c,d
建立索引

db.abc.createIndex({c:1})
db.abc.createIndex({a:1,b:1,c:1})

最合理場景當然是匹配上索引a,b,c這樣即完成了查找又完成了排序

語句 索引 執行過程
db.abc.find({a:{$gt:2},b:1}).sort({c:1}) {c:1} 排序匹配了索引,但是查找沒有,所以先全表掃描用c排序,再使用a,b去回表比較篩選
db.abc.find({a:2,b:1}).sort({c:1}) {a:1,b:1,c:1} 使用索引abc直接去匹配,性能最優

參考文章:
https://docs.mongodb.com/manual/indexes/index.html
https://docs.mongodb.com/manual/tutorial/analyze-query-plan/
https://docs.mongodb.com/manual/reference/operator/query/or/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章