MongoDB索引詳解
一、索引簡介
索引支持在MongoDB中高效執行查詢。沒有索引,MongoDB必須執行集合掃描,即掃描集合中的每個文檔,以選擇與查詢語句匹配的那些文檔。如果查詢存在適當的索引,則MongoDB可以使用該索引來限制它必須檢查的文檔數。
索引是特殊的數據結構,它以易於遍歷的形式存儲集合數據集的一小部分。索引存儲一個特定字段或一組字段的值,按該字段的值排序。索引條目的排序支持有效的相等匹配和基於範圍的查詢操作。另外,MongoDB可以通過使用索引中的順序返回排序的結果。
下圖說明了使用索引選擇和排序匹配文檔的查詢:
從根本上講,MongoDB中的索引類似於其他數據庫系統中的索引。MongoDB在集合級別定義索引,並支持MongoDB集合中文檔的任何字段或子字段的索引。
索引操作基本命令:
名稱 | 描述 |
---|---|
db.collection.createIndex() |
在集合上建立索引。 |
db.collection.dropIndex() |
刪除集合上的指定索引。 |
db.collection.dropIndexes() |
刪除集合上的所有索引。 |
db.collection.getIndexes() |
返回描述集合中現有索引的文檔數組。 |
db.collection.reIndex() |
重建集合上所有現有的索引。 |
db.collection.totalIndexSize() |
報告集合上的索引使用的總大小。提供圍繞輸出totalIndexSize 字段的包裝collStats 。 |
cursor.explain() |
報告有關遊標的查詢執行計劃。 |
cursor.hint() |
強制MongoDB對查詢使用特定的索引。 |
cursor.max() |
指定遊標的排他上限。用於cursor.hint() |
cursor.min() |
指定一個遊標的下限值。用於cursor.hint() |
//查看集合索引
db.collection.getIndexes()
db.collection.getIndexKeys()
//創建索引
db.collection.createIndex()
db.collection.createIndexes()
//刪除索引
db.collection.dropIndex()
db.collection.dropIndexes()
//查詢時指定索引,hint()
db.people.find({ name: "John Doe", zipcode: { $gt: "63000" } }).hint( { zipcode: 1 } )
//查詢時指定不使用任何索引,hint()方法指定操作符$natural
db.people.find({ name: "John Doe", zipcode: { $gt: "63000" } }).hint( { $natural: 1 } )
二、常用索引
先創建一個測試集合,結構如下:
集合score結構如下:
{
"_id": "3300c04a4ad233577f97dc45",
"uid": "uid123",
"name":"zhangsan",
"score": 89,
"tags": ["apple","huawei","mi","samsung"],
"addition": { hobby: "running", character: "steady" },
"address": [
{"area":"北京市","detail":"北京市朝陽區北辰西路12號","code":"1103"},
{"area":"上海市","detail":"上海市人民大道200號","code":"1201"},
{"area":"深圳市","detail":"深圳市福田區深南大道","code":"1312"}
],
"content":"這是一個最好的時代,也是一個最壞的時代",
"remarks":"OOK,好記性不如爛筆頭,學無止境啊",
"loc" : { "type": "Point", "coordinates": [ -73.97, 40.77 ] },
"loc2" : [ 55.5, 42.3 ]
}
1、單鍵索引(Single Field Indexes)
在創建集合期間,MongoDB 在_id字段上默認創建唯一索引。該索引可防止客戶端插入兩個具有相同_id值的文檔。
//爲score集合的score字段創建一個升序索引
db.score.createIndex( { score: 1 } )
//爲score集合的score字段創建一個降序索引
db.score.createIndex( { score: -1 } )
//在子文檔中創建索引
db.score.createIndex( { "addition.character": 1 } )
2、複合索引(Compound Indexes)
複合索引可以支持在多個字段上匹配的查詢。複合索引中列出的字段的順序很重要,這些文檔將首先按uid字段的值排序,然後在該字段的每個uid值內,按score字段的值排序。
複合索引除了支持在所有索引字段上都匹配的查詢之外,還可以支持在索引字段的前綴上匹配的查詢。也就是說,索引支持對uid字段以及uid和score字段的查詢:
//創建uid+score的複合索引
db.score.createIndex( { "uid": 1, "score": 1 } )
//以下查詢將命中複合索引,查詢條件的順序不影響命中索引
db.score.find( { uid: "uid123" } )
db.score.find( { uid: "uid123", score: { $gt: 80 } } )
db.score.find( { score: { $gt: 80 }, uid: "uid123" } )
3、多建索引(Multikey Indexes)
如果任何索引字段是數組,MongoDB都會自動創建一個多鍵索引;您無需顯式指定多鍵類型。
如果索引是多鍵,則索引邊界的計算遵循特殊規則。可參考:https://docs.mongodb.com/manual/core/multikey-index-bounds/
//數組創建索引
db.score.createIndex( { tags: 1 } )
//對象數組創建索引
db.score.createIndex( { "address.area": 1, "address.code": 1 } )
複合多鍵索引:不能創建一個以上數組的複合多鍵索引。如果已經存在複合多鍵索引,則不能插入違反此限制的文檔。
4、文字索引(Text Indexes)
一個集合最多可以有一個text索引。索引數據的默認語言是English。
//文本索引
db.score.createIndex( { content: "text" } )
//複合文本索引
db.score.createIndex( { content: "text", remarks: "text" } )
//指定權重,默認權重爲1。索引字段的權重表示就文本搜索分數而言,該字段相對於其他索引字段的重要性。
//指定索引名稱爲:TextIndex。不指定時默認名稱爲:content_text_remarks_text
db.score.createIndex(
{
content: "text",
remarks: "text"
},
{
weights: {
content: 10,
remarks: 5
},
name: "TextIndex"
}
)
//指定默認語言:西班牙語
db.score.createIndex(
{ content : "text" },
{ default_language: "spanish" }
)
//查詢
db.score.find( { $text: { $search:"OK" } } )
注意
- 複合
text
索引不能包含任何其他特殊索引類型,例如多鍵或地理空間索引字段。 - 如果複合
text
索引在索引鍵之前包含鍵,則要text
執行$text搜索,查詢謂詞必須在前面的鍵上包含相等匹配條件。 - 創建複合
text
索引時,所有text
索引鍵必須在索引規範文檔中相鄰列出。
5、通配符索引(Wildcard Indexes)
MongoDB 4.2引入了通配符索引,以支持針對未知或任意字段的查詢。使用通配符文本索引,MongoDB會爲包含集合中每個文檔的字符串數據的每個字段建立索引。
//所有字段創建索引
db.score.createIndex( { "$**" : 1 } )
//使用通配符說明符創建文本索引,所有文本類型的字段都有此索引
db.score.createIndex( { "$**": "text" } )
//針對以下結構,創建索引以支持對userMetadata的任何子字段的查詢。
{ "userMetadata" : { "likes" : [ "dogs", "cats" ] } }
{ "userMetadata" : { "dislikes" : "pickles" } }
{ "userMetadata" : { "age" : 45 } }
{ "userMetadata" : "inactive" }
//可以支持查詢userMetadata,userMetadata.likes,userMetadata.dislikes,userMetadata.age
db.userData.createIndex( { "userMetadata.$**" : 1 } )
//該索引可以支持以下查詢:
db.userData.find ({ “ userMetadata.likes”:“ dogs” })
db.userData.find ({ “ userMetadata.dislikes”:“ pickles ” })
db.userData.find ({ “ userMetadata.age”:{ $ gt:30 } })
db.userData.find ({ “ userMetadata”:“ inactive” })
注意
- 通配符索引最多可以在任何給定查詢謂詞中支持一個字段。
- 默認情況下,通配符索引省略_id字段。要將_id字段包括在通配符索引中,必須將其明確包含在wildcardProjection文檔中(即{_id:1})
- 可以在一個集合中創建多個通配符索引。
- 通配符索引可能與集合中的其他索引覆蓋相同的字段。
- 通配符索引是稀疏索引,即使索引字段包含空值,也僅包含具有索引字段的文檔的條目。
6、2dsphere Indexes
一個2dsphere索引支持在查詢一個類似地球的球體上計算的幾何形狀。2dsphere索引支持所有MongoDB地理空間查詢:包含,相交和鄰近度查詢。2dsphere索引支持數據存儲爲:GeoJSON和座標對。
//創建2dsphere索引
db.score.createIndex( { loc : "2dsphere" } )
//查詢網格座標,返回經度和緯度在10英里半徑內的所有文檔
db.score.find( { loc :
{ $geoWithin :
{ $centerSphere :
[ [ -88 , 30 ] , 10 / 3963.2 ]
} } } )
7、2d Indexes
2d索引用於存儲爲二維平面上的點的數據。默認情況下,2d索引採用經度和緯度,並且邊界爲[-180,180)。默認邊界允許應用程序插入無效緯度大於90或小於-90的文檔。未定義具有此類無效點的地理空間查詢的行爲。
//創建2d索引
db.collection.createIndex( { <location field> : "2d" } ,
{ min : <lower bound> , max : <upper bound> } )
//創建
db.score.createIndex( { "loc2": "2d" } )
//查詢在由左下角和右上角定義的矩形內的文檔
db.places.find( { loc2 :
{ $geoWithin :
{ $box : [ [ 0 , 0 ] ,
[ 100 , 100 ] ]
} } } )
//查詢以居中爲半徑且半徑爲的圓內的文檔
db.places.find( { loc2: { $geoWithin :
{ $center : [ [-74, 40.74 ] , 10 ]
} } } )
8、哈希索引(Hashed Indexes)
db.collection.createIndex( { _id: "hashed" } )
注意
MongoDB支持Hashed任何單個字段的索引。散列函數摺疊嵌入式文檔並計算整個值的散列,但不支持多鍵(即數組)索引。
您不能創建具有Hashed索引字段的複合索引,也不能在索引上指定唯一約束Hashed。
三、索引屬性
1、TTL Indexes
TTL索引是特殊的單字段索引,MongoDB可使用TTL索引在一定時間後或在特定時鐘時間自動從集合中刪除文檔。數據到期對於某些類型的信息很有用,例如機器生成的事件數據,日誌和會話信息,這些信息僅需要在數據庫中保留有限的時間。
db.eventlog.createIndex( { "lastModifiedDate": 1 }, { expireAfterSeconds: 3600 } )
2、Unique Indexes
唯一索引可確保索引字段不存儲重複值;即對索引字段強制唯一性。默認情況下,MongoDB 在創建集合期間會在_id字段上創建唯一索引。
db.score.createIndex( { "uid": 1 }, { unique: true } )
db.score.createIndex( { uid: 1, name: 1 }, { unique: true } )
3、Partial Indexes
部分索引僅索引集合中符合指定過濾器表達式的文檔。通過索引集合中文檔的子集,部分索引具有較低的存儲需求,並降低了索引創建和維護的性能成本。
//score值大於60的才創建索引
db.score.createIndex(
{ score: 1 },
{ partialFilterExpression: { score: { $gt: 60 } } }
)
//查詢score大於60的文檔,可使用索引
db.score.find( { score: { $gte: 60 } } )
//查詢score小於80的文檔,不可使用索引,因爲使用索引會導致結果集不完整
db.score.find( { score: { $lt: 80 } } )
4、Case Insensitive Indexes
不區分大小寫的索引支持在不考慮大小寫的情況下執行字符串比較的查詢。
db.createCollection("fruit")
//創建帶有排序規則的索引,並將strength 參數設置爲1或2
//locale:指定語言規則。
//strength:確定比較規則。值爲1或2表示不區分大小寫的排序規則。
db.fruit.createIndex( { type: 1},
{ collation: { locale: 'en', strength: 2 } } )
db.fruit.insert( [ { type: "apple" },
{ type: "Apple" },
{ type: "APPLE" } ] )
db.fruit.find( { type: "apple" } ) // does not use index, finds one result
db.fruit.find( { type: "apple" } ).collation( { locale: 'en', strength: 2 } )
// 使用索引,查詢結果3條
db.fruit.find( { type: "apple" } ).collation( { locale: 'en', strength: 1 } )
// 未使用索引,查詢結果3條
5、Sparse Indexes
稀疏索引僅包含具有索引字段的文檔條目,即使索引字段包含空值也是如此。索引會跳過缺少索引字段的所有文檔。索引是“稀疏的”,因爲它不包括集合的所有文檔。
從MongoDB 3.2開始,MongoDB提供了創建部分索引的選項 。部分索引提供了稀疏索引功能的超集。應優先使用部分索引而不是稀疏索引。
db.score.createIndex( { "name": 1 }, { sparse: true } )
四、索引構建過程
下表描述了索引構建過程的每個階段:
階段 | 描述 |
---|---|
鎖 | mongod 在被索引的集合上獲得獨佔的X鎖。這將阻止對集合的所有讀取和寫入操作,包括任何針對該集合的複製的寫入操作或元數據命令。mongod沒有 釋放這個鎖。 |
初始化 |
|
鎖 | mongod 將集合上的獨佔X 鎖降級爲專用IX 鎖。mongod 將定期得到的這個鎖交錯讀取和寫入操作。 |
掃描集合 |
對於集合中的每個文檔, 如果 如果 一旦 |
處理側寫表 |
如果 如果 對於在構建過程中寫入集合的每個文檔, |
鎖 | mongod 將集合上的專用IX 鎖升級爲共享S 鎖。這將阻止對集合的所有寫操作,包括任何針對該集合的複製寫操作或元數據命令。 |
完成臨時側寫表的處理 |
如果 如果 |
鎖 | mongod 將集合上的共享S 鎖升級爲集合上的獨佔X 鎖。這將阻止對集合的所有讀取和寫入操作,包括任何針對該集合的複製的寫入操作或元數據命令。mongod 沒有釋放這個鎖。 |
銷燬側寫表 |
如果 如果 此時,索引包括寫入集合的所有數據。 |
處理約束違規表 |
如果約束衝突表中的任何鍵仍然產生重複鍵錯誤, 一旦約束衝突表被清空或在處理過程中遇到重複的鍵衝突, |
將索引標記爲就緒 | mongod 更新索引元數據,將索引標記爲可以使用。 |
鎖 | mongod 釋放X 的集合鎖。 |