MongoDB系列--輕鬆應對面試中遇到的MongonDB索引(index)問題

Ccww發表於2019-08-03

  索引是特殊的資料結構,索引儲存在一個易於遍歷讀取的資料集合中( 索引儲存在特定欄位或欄位集的值),而且是使用了B-tree結構。索引可以極大程度提升MongoDB查詢效率。
  如果沒有索引,MongoDB必須執行全集合collections掃描,即掃描集合中的每個文件,選取符合查詢條件的文件document。 如果查詢時存在適當的索引,MongoDB可以使用索引來限制它必須查詢的文件document的數量,特別是在處理大量資料時,所以選擇正確的索引是很關鍵的、重要的

建立索引,需要考慮的問題:

  • 每個索引至少需要資料空間為8kb;
  • 新增索引會對寫入操作會產生一些效能影響。 對於具有高寫入率的集合Collections,索引很昂貴,因為每個插入也必須更新任何索引;
  • 索引對於具有高讀取率的集合Collections很有利,不會影響沒索引查詢;
  • 處於索引處於action狀態時,每個索引都會佔用磁碟空間和記憶體,因此需要對這種情況進行跟蹤檢測。

索引限制:

  • 索引名稱長度不能超過128欄位;
  • 複合索引不能超過32個屬性;
  • 每個集合Collection不能超過64個索引;
  • 不同型別索引還具有各自的限制條件。

1. 索引管理

1.1 索引建立

索引建立使用createIndex()方法,格式如下:

db.collection.createIndex(<key and index type specification>,<options>)
複製程式碼

createIndex() 接收可選引數,可選引數列表如下:

Parameter Type Description
background Boolean 建索引過程會阻塞其它資料庫操作,background可指定以後臺方式建立索引,即增加 "background" 可選引數。 "background" 預設值為false。
unique Boolean 建立的索引是否唯一。指定為true建立唯一索引。預設值為false.
name string 索引的名稱。如果未指定,MongoDB的通過連線索引的欄位名和排序順序生成一個索引名稱。
dropDups Boolean 3.0+版本已廢棄。在建立唯一索引時是否刪除重複記錄,指定 true 建立唯一索引。預設值為 false.
sparse Boolean 對文件中不存在的欄位資料不啟用索引;這個引數需要特別注意,如果設定為true的話,在索引欄位中不會查詢出不包含對應欄位的文件.。預設值為 false.
expireAfterSeconds integer 指定一個以秒為單位的數值,完成 TTL設定,設定集合的生存時間。
v index version 索引的版本號。預設的索引版本取決於mongod建立索引時執行的版本。
weights document 索引權重值,數值在 1 到 99,999 之間,表示該索引相對於其他索引欄位的得分權重。
default_language string 對於文字索引,該引數決定了停用詞及詞幹和詞器的規則的列表。 預設為英語
language_override string 對於文字索引,該引數指定了包含在文件中的欄位名,語言覆蓋預設的language,預設值為 language.

1.2 檢視索引

檢視Collection中所有索引,格式如下:

db.collection.getIndexes()
複製程式碼

1.3 刪除索引

刪除Collection中的索引:格式如下:

db.collection.dropIndexes()   //刪除所有索引
db.collection.dropIndex()    //刪除指定的索引  
複製程式碼

1.4 索引名稱

索引的預設名稱是索引鍵和索引中每個鍵的value1或-1,形式index_name+1/-1,比如:

db.products.createIndex( { item: 1, quantity: -1 } )----索引名稱為item_1_quantity_-1
複製程式碼

也可以指定索引名稱:

db.products.createIndex( { item: 1, quantity: -1 } , { name: "inventory" } )  ----索引名稱為inventory
複製程式碼

1.5 檢視索引建立過程以及終止索引建立

方法 解析
db.currentOp() 檢視索引建立過程
db.killOp(opid) 終止索引建立,其中-opid為操作id

1.6 索引使用情況

形式 解析
$indexStats 獲取索引訪問資訊
explain() 返回查詢情況:在executionStats模式下使用db.collection.explain()或cursor.explain()方法返回有關查詢過程的統計資訊,包括使用的索引,掃描的文件數以及查詢處理的時間(以毫秒為單位)。
Hint() 控制索引,例如要強制MongoDB使用特定索引進行db.collection.find()操作,請使用hint()方法指定索引

1.7 MongoDB度量標準

MongoDB提供了許多索引使用和操作的度量標準,在分析資料庫的索引使用時可能需要考慮這些度量標準,如下所示:

形式 解析
metrics.queryExecutor.scanned 在查詢和查詢計劃評估期間掃描的索引項的總數
metrics.operation.scanAndOrder 返回無法使用索引執行排序操作的已排序數字的查詢總數
collStats.totalIndexSize 所有索引的總大小。 scale引數會影響此值。如果索引使用字首壓縮(這是WiredTiger的預設值),則返回的大小將反映計算總計時任何此類索引的壓縮大小。
collStats.indexSizes 指定集合collection上每個現有索引的鍵和大小。 scale引數會影響此值
dbStats.indexes 包含資料庫中所有集合的索引總數的計數。
dbStats.indexSize 在此資料庫上建立的所有索引的總大小

1.8 後臺索引操作

  在密集(快達到資料庫最大容量)Collection建立索引:在預設情況下,在密集的Collection(快達到資料庫最大容量)時建立索引,會阻止其他操作。在給密集的Collection(快達到資料庫最大容量)建立索引時, 索引構建完成之前,儲存Collection的資料庫不可用於讀取或寫入操作。 任何需要對所有資料庫(例如listDatabases)進行讀或寫鎖定的操作都將等待不是後臺程式的索引構建完成。

因此可以使用background屬性進行設定後臺索引建立,操作如下:

db.people.createIndex( { zipcode: 1 }, { background: true } )
預設情況下,在建立索引時,background為false,可以和其他屬性進行組合使用:
db.people.createIndex( { zipcode: 1 }, { background: true, sparse: true } )
複製程式碼

2. 索引型別

2.1 單欄位索引(Single Field Indexes)

  MongoDB可以在任何一個欄位中建立索引,預設情況下,所有的集合(collections)會在_id欄位中建立索引。_id索引是為防止客戶端插入具有相同value的_id欄位的文件Document,而且不能刪除_id欄位索引。
  在分片群集中使用_id索引,如果不使用_id欄位作為分片鍵,則應用程式必須確保_id欄位中值的唯一性以防止出錯,解決方法為使用標準的自動生成的ObjectId來完成。
  一般單欄位索引的value中,“1”指定按升序對專案進行排序的索引,“-1”指定按降序對專案進行排序的索引。如下所示:

MongoDB系列--輕鬆應對面試中遇到的MongonDB索引(index)問題

在單個欄位建立索引,示例如下:

{
  "_id": ObjectId("570c04a4ad233577f97dc459"),
  "score": 1034,
  "location": { state: "NY", city: "New York" }
}
//建立單欄位索引
 db.records.createIndex( { score: 1 } )
 //支援的查詢  
db.records.find( { score: 2 } )
db.records.find( { score: { $gt: 10 } } )
複製程式碼

在嵌入式文件Document中的欄位建立索引,示例如下:

db.records.createIndex( { "location.state": 1 } )
//支援的查詢 
db.records.find( { "location.state": "CA" } )
db.records.find( { "location.city": "Albany", "location.state": "NY" } )
複製程式碼

在嵌入式文件Document建立索引,示例如下:

db.records.createIndex( { location: 1 } )
//支援查詢 
db.records.find( { location: { city: "New York", state: "NY" } } )
複製程式碼

2.2 複合索引(Compound Index)

複合索引指的是將多個key組合到一起建立索引,這樣可以加速匹配多個鍵的查詢。特性如下:

  • MongoDB對任何複合索引都限制了32個欄位;
  • 無法建立具有雜湊索引(hash index)型別的複合索引。如果嘗試建立包含雜湊索引欄位的複合索引,則會報錯;
  • 複合索引建立欄位索引的順序是很重要的。因為索引以升序(1)或降序(-1)排序順序儲存對欄位的引用; 對於單欄位索引,鍵的排序順序無關緊要,因為MongoDB可以在任一方向上遍歷索引。 但是,對於複合索引,排序順序可以決定索引是否可以支援排序操作;
  • 除了支援在所有索引欄位上匹配的查詢之外,複合索引還可以支援與索引欄位的字首匹配的查詢。

建立複合索引的格式:

db.collection.createIndex( { <field1>: <type>, <field2>: <type2>, ... } )
複製程式碼

排序順序,兩個欄位的複合索引示例,index{userid:1,score:-1},先userid的value排序,然後再userid排序基礎下進行score排序。如下圖:

MongoDB系列--輕鬆應對面試中遇到的MongonDB索引(index)問題
MongoDB系列--輕鬆應對面試中遇到的MongonDB索引(index)問題

建立複合索引,示例如下:

{
 "_id": ObjectId(...),
 "item": "Banana",
 "category": ["food", "produce", "grocery"],
 "location": "4th Street Store",
 "stock": 4,
 "type": "cases"
}
//建立複合索引
db.products.createIndex( { "item": 1, "stock": 1 } )
//支援的查詢
db.products.find( { item: "Banana" } )
db.products.find( { item: "Banana", stock: { $gt: 5 } } )
複製程式碼

複合索引中的字首查詢,示例如下:

//建立複合索引
db.products.createIndex({ "item": 1, "location": 1, "stock": 1 })
//字首為:{ item: 1 }與{ item: 1, location: 1 }
//支援字首查詢為   
 db.products.find( { item: "Banana" } )
 db.products.find( { item: "Banana", location: “beijing”} )
//不支援字首查詢,不會提高查詢效率
//不包含字首欄位
 db.products.find( { location: “beijing”} )
 db.products.find( { stock: { $gt: 5 } )
 db.products.find( { location: “beijing”,stock: { $gt: 5 } )
 //不按照建立複合索引欄位順序的字首查詢
 db.products.find( { location: “beijing”,item: "Banana" },stock: { $gt: 5 } )
複製程式碼

2.3 多鍵索引

  MongoDB使用多鍵索引為陣列的每個元素都建立索引,多鍵索引可以建立在字串、數字等key或者內嵌文件(document)的陣列上,如果索引欄位包含陣列值,MongoDB會自動確定是否建立多鍵索引; 您不需要手動指定多鍵型別。 其中建立方式:

db.coll.createIndex( { <field>: < 1 or -1 > } )
複製程式碼

索引邊界
  使用多鍵索引,會出現索引邊界(索引邊界即是查詢過程中索引能查詢的範圍)的計算,並計算必須遵循一些規則。即當多個查詢的條件中欄位都存在索引中時,MongoDB將會使用交集或者並集等來判斷這些條件索引欄位的邊界最終產生一個最小的查詢範圍。可以分情況:
1).交集邊界
  交集邊界即為多個邊界的邏輯交集,對於給定的陣列欄位,假定一個查詢使用了陣列的多個條件欄位並且可以使用多鍵索引。如果使用了$elemMatch連線了條件欄位,則MongoDB將會相交多鍵索引邊界,示例如下:

//survey Collection中document有一個item欄位和一個ratings陣列欄位
{ _id: 1, item: "ABC", ratings: [ 2, 9 ] } 
{ _id: 2, item: "XYZ", ratings: [ 4, 3 ] }

//在ratings陣列上建立多鍵索引: 
db.survey.createIndex({ratings:1})

//兩種查詢
db.survey.find({ratings:{$elemMatch:{$gte:3,$lte:6}}})  //(1)
db.survey.find( { ratings : { $gte: 3, $lte: 6 } } )  //(2)
複製程式碼

  查詢條件分別為大於等於3、小於等於6,其中 (1)中使用了$elemMatch連線查詢條件,會產生一個交集ratings:[[3,6]。在(2)查詢中,沒使用$elemMatch,則不會產生交集,只要滿足任何一個條件即可。

2).並集邊界
  並集邊界常常用在確定多鍵組合索引的邊界,例如:給定的組合索引{a:1,b:1},在欄位a上有一個邊界:[3,+∞),在欄位b上有一個邊界:(-∞,6],相併這兩個邊界的結果是:{ a: [ [ 3, Infinity ] ], b: [ [ -Infinity, 6 ] ] }。

  而且如果MongoDB沒法並集這兩個邊界,MongoDB將會強制使用索引的第一個欄位的邊界來進行索引掃描,在這種情況下就是: a: [ [ 3, Infinity ] ]。

3、陣列欄位的組合索引
  一個組合索引的索引欄位是陣列,例如一個survey collection集合document文件中含有item欄位和ratings陣列欄位,示例如下:

{ _id: 1, item: "ABC", ratings: [ 2, 9 ] } 
{ _id: 2, item: "XYZ", ratings: [ 4, 3 ] }

//在item欄位和ratings欄位建立一個組合索引:
db.survey.createIndex( { item: 1, ratings: 1 } )

//查詢條件索引包含的兩個key
db.survey.find( { item: "XYZ", ratings: { $gte: 3 } } )
複製程式碼

分別處理查詢條件:

item: "XYZ" -->  [ [ "XYZ", "XYZ" ] ];
ratings: { $gte: 3 } -->  [ [ 3, Infinity ] ].
複製程式碼

MongoDB使用並集邊界來組合這兩個邊界:

{ item: [ [ "XYZ", "XYZ" ] ], ratings: [ [ 3, Infinity ] ] }  
複製程式碼

4).內嵌文件document的陣列上建立組合索引
  如果陣列中包含內嵌文件document,想在包含的內嵌文件document欄位上建立索引,需要在索引宣告中使用逗號“,” 來分隔欄位名,示例如下:

ratings: [ { score: 2, by: "mn" }, { score: 9, by: "anon" } ]
複製程式碼

則score欄位名稱就是:ratings.score。

5).混合不是陣列型別的欄位和陣列型別欄位的並集

{ _id: 1, item: "ABC", ratings: [ { score: 2, by: "mn" }, { score: 9, by: "anon" } ] } 
{ _id: 2, item: "XYZ", ratings: [ { score: 5, by: "anon" }, { score: 7, by: "wv" } ] }

//在item和陣列欄位ratings.score和ratings.by上建立一個組合索引
db.survey2.createIndex( { "item": 1, "ratings.score": 1, "ratings.by": 1 } )

//查詢
db.survey2.find( { item: "XYZ", "ratings.score": { $lte: 5 }, "ratings.by": "anon" } )
複製程式碼

分別對查詢條件進行處理:

item: "XYZ"--> [ ["XYZ","XYZ"] ];
score: {$lte:5}--> [[-Infinity,5]];
by: "anon" -->["anon","anon"].
複製程式碼

  MongoDB可以組合 item鍵的邊界與 ratings.score和ratings.by兩個邊界中的一個,到底是score還是by索引邊界這取決於查詢條件和索引鍵的值。MongoDB不能確保哪個邊界和item欄位進行並集。 但如果想組合ratings.score和ratings.by邊界,則查詢必須使用$elemMatch

6).陣列欄位索引的並集邊界
  在陣列內部並集索引鍵的邊界,

  • 除了欄位名稱外,索引鍵必須有相同的欄位路徑,
  • 查詢的時候必須在路徑上使用$elemMatch進行宣告
  • 對於內嵌的文件,使用逗號分隔的路徑,比如a.b.c.d是欄位d的路徑。為了在相同的陣列上並集索引鍵的邊界,需要$elemMatch必須使用在a.b.c的路徑上。

比如:在ratings.score和ratings.by欄位上建立組合索引:

db.survey2.createIndex( { "ratings.score": 1, "ratings.by": 1 } )
複製程式碼

欄位ratings.score和ratings.by擁有共同的路徑ratings。下面的查詢使用$elemMatch則要求ratings欄位必須包含一個元素匹配這兩個條件:

db.survey2.find( { ratings: { $elemMatch: { score: { $lte: 5 }, by: "anon" } } } )
複製程式碼

分別對查詢條件進行處理:

score: { $lte: 5 } --> [ -Infinity, 5 ];
by: "anon"--> [ "anon", "anon" ].
複製程式碼

MongoDB可以使用並集邊界來組合這兩個邊界:

{ "ratings.score" : [ [ -Infinity, 5 ] ], "ratings.by" : [ [ "anon", "anon" ] ] }  
複製程式碼

7). 還有不使用$elemMatch進行查詢以及不完整的路徑上使用$elemMatch,想要了解更多可以檢視《官方文件-Multikey Index Bounds》。


限制:

  • 對於一個組合多鍵索引,每個索引文件最多隻能有一個索引欄位的值是陣列。如果組合多鍵索引已經存在了,不能在插入文件的時候違反這個限制;
  • 不能宣告一個多鍵索引作為分片鍵索引;
  • 雜湊索引不能擁有多鍵索引;
  • 多鍵索引不能進行覆蓋查詢;
  • 當一個查詢宣告把陣列整體作為精確匹配的時候,MongoDB可以使用多鍵索引來查詢這個查詢陣列的第一個元素,但是不能使用多鍵索引掃描來找出整個陣列。代替方案是當使用多鍵索引查詢出陣列的第一個元素之後,MongoDB再對過濾之後的文件再進行一次陣列匹配。

2.4 全文索引(text index)

  MongoDB提供了一種全文索引型別,支援在Collection中搜尋字串內容,對字串與字串陣列建立全文可搜尋的索引 。 這些全文索引不儲存特定於語言的停用詞(例如“the”,“a”,“或”),並且阻止document集合中的單詞僅儲存根詞。建立方式如下:

db.collection.createIndex( { key: "text",key:"text" ..... } )
複製程式碼

而且MongoDB提供權重以及萬用字元的建立方式。查詢方式多個字串空格隔開,排除查詢使用“-”如下所示:

db.collection.find({$text:{$search:"runoob add -cc"}})
複製程式碼

要刪除全本索引,需要將索引的名稱傳遞給db.collection.dropIndex()方法, 而要獲取索引的名稱,使用db.collection.getIndexes()方法。

  還可以指定全文索引的語言,通過default_language屬性 在建立時指定, 或者使用language_override屬性 覆蓋掉建立document文件時預設的語言,如下所示:

//指定不同的語言的方法:建立全文索引的時候使用default_language屬性
db.collection.createIndex(
  { content : "text" },
  { default_language: "spanish" })
  
//使用language_override屬性覆蓋預設的語言
db.quotes.createIndex( { quote : "text" },
                   { language_override: "idioma" } )
                   
//預設的全文索引名稱為context_text,users.comments.text,指定名稱MyTextIndex
db.collection.createIndex(
   {
     content: "text",
     "users.comments": "text",
     "users.profiles": "text"
   },
   {
     name: "MyTextIndex"
   }
)
複製程式碼

權重
  每個全文索引可以通過設定權重來分配不同的搜尋程度,預設權重為1,對於文件中的每個索引欄位,MongoDB將匹配數乘以權重並將結果相加。 使用此總和,MongoDB然後計算文件的分數,示例如下:

{
  _id: 1,
  content: "This morning I had a cup of coffee.",
  about: "beverage",
  keywords: [ "coffee" ]
}
{
  _id: 2,
  content: "Who doesn't like cake?",
  about: "food",
  keywords: [ "cake", "food", "dessert" ]
}

//通過db.blog.createIndex來指定weight權重
db.blog.createIndex(
   {
     content: "text",
     keywords: "text",
     about: "text"
   },
   {
     weights: {
       content: 10,
       keywords: 5
     },
     name: "TextIndex"
   }
 )
複製程式碼

content權重為10,keywords為5,about為預設權重1,因此可以得出content對於keywords查詢頻率高於2倍,而對於about欄位則是10倍。

萬用字元全文索引
  在多個欄位上建立全文索引時,還可以使用萬用字元說明符($**)。 使用萬用字元全文索引,MongoDB會為包含Collection中每個Document的字串資料。例如:

db.collection.createIndex( { "$**": "text" } )
複製程式碼

萬用字元全本索引是多個欄位上的全本索引。 因此,可以在建立索引期間為特定欄位指定權重,以控制結果的排名。

限制

  • 每個Collection一個全文索引:一個collection最多隻有一個全文索引,
  • Text Search 和Hints函式,如果查詢包含$ text查詢表示式,則不能使用hint();
  • Text Index and Sort,排序操作無法從文字索引獲取排序順序,即使是複合文字索引也是如此; 即排序操作不能使用文字索引中的排序;
  • 複合索引:複合索引可以包括文字索引鍵與升序/降序索引鍵的組合。 但是,這些複合索引具有以下限制:
    1).複合文字索引不能包含任何其他特殊索引型別,例如多鍵或地理空間索引欄位。
    2).如果複合文字索引包括文字索引鍵之前的鍵,則執行$ text搜尋時,查詢謂詞必須包含前面鍵上的相等匹配條件。
    3).建立複合文字索引時,必須在索引規範文件中相鄰地列出所有文字索引鍵。

2.5 Hash 索引

  雜湊索引使用雜湊函式來計算索引欄位值的雜湊值。 雜湊函式會摺疊嵌入的文件並計算整個值的雜湊值,但不支援多鍵(即陣列)索引。 生成hash索引key使用了convertShardKeyToHashed()方法。建立方式如下:

db.collection.createIndex( { _id: "hashed" } )
複製程式碼

而且雜湊索引支援使用雜湊分片鍵進行分片。 基於雜湊的分片使用欄位的雜湊索引作為分片鍵來分割整個分片群集中的資料。

3. 索引屬性

索引屬性有TTL索引、惟一性索引、部分索引、稀疏索引以及區分大小寫索引。

3.1 TTL索引(TTL Indexes)

  TTL索引是特殊的單欄位索引,並且欄位型別必須是date型別或者包含有date型別的陣列,MongoDB可以使用它在一定時間後或在特定時鐘時間自動從集合中刪除文件。 資料到期對於某些型別的資訊非常有用,例如機器生成的事件資料,日誌和會話資訊,這些資訊只需要在資料庫中持續有限的時間。

建立TTL索引方法,和普通索引的建立方法一樣,只是會多加一個expireAfterSeconds的屬性,格式如下:

db.collection.createIndex( {key and index type specification},{ expireAfterSeconds: time})
複製程式碼

例子:

db.eventlog.createIndex( { "lastModifiedDate": 1 }, { expireAfterSeconds: 3600 } )
複製程式碼

指定過期時間
首先在儲存BSON日期型別值或BSON日期型別物件陣列的欄位上建立TTL索引,並指定expireAfterSeconds值為0.對於集合中的每個文件,設定 索引日期欄位為與文件到期時間對應的值。示例操作如下:
第一步:

db.log_events.createIndex( { "expireAt": 1 }, { expireAfterSeconds: 0 } )
複製程式碼

第二步:

db.log_events.insert( {
   "expireAt": new Date('July 22, 2013 14:00:00'),
   "logEvent": 2,
   "logMessage": "Success!"
} )
複製程式碼

資料過期型別:

  • 當指定時間到了過期的閾值資料就會過期並刪除;
  • 如果欄位是陣列,並且索引中有多個日期值,MongoDB使用陣列中最低(即最早)的日期值來計算到期閾值;
  • 如果文件(document)中的索引欄位不是日期或包含日期值的陣列,則文件(document)將不會過期;
  • 如果文件(document)不包含索引欄位,則文件(document)不會過期。

TTL索引特有限制:

  • TTL索引是單欄位索引。 複合索引不支援TTL並忽略expireAfterSeconds選項;
  • _id屬性不支援TTL索引;
  • 無法在上限集合上建立TTL索引,因為MongoDB無法從上限集合中刪除文件;
  • 不能使用createIndex()方法來更改現有索引的expireAfterSeconds值。而是將collMod資料庫命令與索引集合標誌結合使用。 否則,要更改現有索引的選項的值,必須先刪除索引並重新建立;
  • 如果欄位已存在非TTL單欄位索引,則無法在同一欄位上建立TTL索引,因為無法在相同key建立不同型別的的索引。 要將非TTL單欄位索引更改為TTL索引,必須先刪除索引並使用expireAfterSeconds選項重新建立。

3.2 惟一性索引(Unique Indexes)

  唯一索引可確保索引欄位不儲存重複值; 即強制索引欄位的唯一性。 預設情況下,MongoDB在建立集合期間在_id欄位上建立唯一索引。建立方式如下:

db.collection.createIndex( <key and index type specification>, { unique: true } )
複製程式碼

單個欄位建立方式,示例如下:

db.members.createIndex( { "user_id": 1 }, { unique: true } )
複製程式碼

唯一性複合索引: 還可以對複合索引強制執行唯一約束。 如果對複合索引使用唯一約束,則MongoDB將對索引鍵值的組合強制實施唯一性。示例如下:

//建立的索引且強制groupNumber,lastname和firstname值組合的唯一性。
db.members.createIndex( { groupNumber: 1, lastname: 1, firstname: 1 }, { unique: true } )
複製程式碼

唯一多鍵索引:

{ _id: 1, a: [ { loc: "A", qty: 5 }, { qty: 10 } ] }

//建立索引:
db.collection.createIndex( { "a.loc": 1, "a.qty": 1 }, { unique: true } )

//插入資料:唯一索引允許將以下Document插入Collection中,因為索引強制執行a.loc和a.qty值組合的唯一性:
db.collection.insert( { _id: 2, a: [ { loc: "A" }, { qty: 5 } ] } )
db.collection.insert( { _id: 3, a: [ { loc: "A", qty: 10 } ] } )
複製程式碼

建立唯一索引到副本或者分片中: 對於副本集和分片叢集,使用滾動過程建立唯一索引需要在過程中停止對集合的所有寫入。 如果在過程中無法停止對集合的所有寫入,請不要使用滾動過程。 相反,通過以下方式在集合上構建唯一索引:

  • 在主伺服器上為副本集發出db.collection.createIndex()
  • 在mongos上為分片群集發出db.collection.createIndex()

NOTE:詳細解析可以看

限制:

  • 如果集合已經包含超出索引的唯一約束的資料(即有重複資料),則MongoDB無法在指定的索引欄位上建立唯一索引。
  • 不能在hash索引上建立唯一索引
  • 唯一約束適用於Collection中的一個Document。由於約束適用於單文件document,因此對於唯一的多鍵索引,只要該文件document的索引鍵值不與另一個文件document的索引鍵值重複,文件就可能具有導致重複索引鍵值的陣列元素。 在這種情況下,重複索引記錄僅插入索引一次。
  • 分片Collection唯一索引只能如下:
    1).分片鍵上的索引
    2).分片鍵是字首的複合索引
    3). 預設的_id索引; 但是,如果_id欄位不是分片鍵或分片鍵的字首,則_id索引僅對每個分片強制執行唯一性約束。如果_id欄位不是分片鍵,也不是分片鍵的字首,MongoDB希望應用程式在分片中強制執行_id值的唯一性。

3.3 部分索引(Partial Indexes)

  部分索引通過指定的過濾表示式去達到區域性搜尋。通過db.collection.createIndex()方法中增加partialFilterExpression屬性建立,過濾表示式如下:

  • 等式表示式(即 file:value或使用$eq運算子)
  • $exists表示式
  • $gt,$gte,$lt,$lte 表示式
  • $type表示式
  • $and

示例如下

//建立部分索引
db.restaurants.createIndex(
   { cuisine: 1, name: 1 },
   { partialFilterExpression: { rating: { $gt: 5 } } }
)
//查詢情況分類
db.restaurants.find( { cuisine: "Italian", rating: { $gte: 8 } } )   //(1)
db.restaurants.find( { cuisine: "Italian", rating: { $lt: 8 } } )    //(2)
db.restaurants.find( { cuisine: "Italian" } )                        //(3)
複製程式碼

其中:

  • (1)查詢: 查詢條件{ $gte: 8 }於建立索引條件{ $gt: 5 }可以構成一個完整集(查詢條件是建立索引條件的子集,即大於5可以包含大於等於 8),可以使用部分索引查詢。
  • (2)查詢: 條件達不到完整集,MongoDB將不會將部分索引用於查詢或排序操作。
  • (3)查詢: 次查詢沒有使用過濾表示式,也不會使用部分索引,因為要使用部分索引,查詢必須包含過濾器表示式(或指定過濾器表示式子集的已修改過濾器表示式)作為其查詢條件的一部分

限制:

  • 不可以僅通過過濾表示式建立多個區域性索引;
  • 不可以同時使用區域性索引和稀疏索引(sparse index);
  • _id索引不能使用區域性索引,分片索引(shard key index)也不能使用區域性索引;
  • 同時指定partialFilterExpression和唯一約束,則唯一約束僅適用於滿足過濾器表示式的文件。 如果Document不符合篩選條件,則具有唯一約束的部分索引是允許插入不符合唯一約束的Document。

3.4 稀疏索引(Sparse Indexes)

  稀疏索只引搜尋包含有索引欄位的文件的條目,跳過索引鍵不存在的文件,即稀疏索引不會搜尋不包含稀疏索引的文件。預設情況下, 2dsphere (version 2), 2d, geoHaystack, 全文索引等總是稀疏索引。建立方式db.collection.createIndex()方法增加sparse屬性,如下所示:

db.addresses.createIndex( { "xmpp_id": 1 }, { sparse: true } )
複製程式碼

稀疏索引不被使用的情況: 如果稀疏索引會導致查詢和排序操作的結果集不完整,MongoDB將不會使用該索引,除非hint()示顯式指定索引。

稀疏複合索引:

  • 對於包含上升/下降排序的稀疏複合索引,只要複合索引中的一個key 索引存在都會被檢測出來
  • 對於包含上升/下降排序的包含地理空間可以的稀疏複合索引,只有存在地理空間key才能被檢測出來
  • 對於包含上升/下降排序的全文索引的稀疏複合索引,只有存在全文索引索引才可以被檢測

稀疏索引與唯一性: 一個既包含稀疏又包含唯一的索引避免集合上存在一些重複值得文件,但是允許多個文件忽略該鍵。滿足稀疏索引和唯一性操作其兩個限制都要遵循。

整合示例如下:

{ "_id" : ObjectId("523b6e32fb408eea0eec2647"), "userid" : "newbie" }
{ "_id" : ObjectId("523b6e61fb408eea0eec2648"), "userid" : "abby", "score" : 82 }
{ "_id" : ObjectId("523b6e6ffb408eea0eec2649"), "userid" : "nina", "score" : 90 }

//在score中建立稀疏索引: 
db.scores.createIndex( { score: 1 } , { sparse: true } )

//查詢
db.scores.find( { score: { $lt: 90 } } )    //(1)
db.scores.find().sort( { score: -1 } )      //(2)
db.scores.find().sort( { score: -1 } ).hint( { score: 1 } ) //(3)

//在score欄位上建立具有唯一約束和稀疏過濾器的索引:
db.scores.createIndex( { score: 1 } , { sparse: true, unique: true } )

//該索引允許插入具有score欄位的唯一值的文件或不包括得分欄位的文件。如下:
db.scores.insert( { "userid": "AAAAAAA", "score": 43 } )
db.scores.insert( { "userid": "BBBBBBB", "score": 34 } )
db.scores.insert( { "userid": "CCCCCCC" } )
db.scores.insert( { "userid": "DDDDDDD" } )

//索引不允許新增以下文件,因為已存在score值為82和90的文件
db.scores.insert( { "userid": "AAAAAAA", "score": 82 } )
db.scores.insert( { "userid": "BBBBBBB", "score": 90 } )
複製程式碼

其中:

  • (1)查詢: 可以使用稀疏索引查詢,返回完整集:{ "_id" : ObjectId("523b6e61fb408eea0eec2648"), "userid" : "abby", "score" : 82 }
  • (2)查詢: 即使排序是通過索引欄位進行的,MongoDB也不會選擇稀疏索引來完成查詢以返回完整的結果:
    { "_id" : ObjectId("523b6e6ffb408eea0eec2649"), "userid" : "nina", "score" : 90 }
    { "_id" : ObjectId("523b6e61fb408eea0eec2648"), "userid" : "abby", "score" : 82 }
    { "_id" : ObjectId("523b6e32fb408eea0eec2647"), "userid" : "newbie" }
  • (3)查詢: 使用hint()返回所需完整集:
    { "_id" : ObjectId("523b6e6ffb408eea0eec2649"), "userid" : "nina", "score" : 90 }
    { "_id" : ObjectId("523b6e61fb408eea0eec2648"), "userid" : "abby", "score" : 82 }

4. 其他事項

4.1 索引策略

索引策略:

  • 應用程式的最佳索引必須考慮許多因素,包括期望查詢的型別,讀取與寫入的比率以及系統上的可用記憶體量。
  • 在開發索引策略時,您應該深入瞭解應用程式的查詢。在構建索引之前,對映將要執行的查詢型別,以便您可以構建引用這些欄位的索引。索引具有效能成本,但是對於大型資料集上的頻繁查詢而言,它們的價值更高。考慮應用程式中每個查詢的相對頻率以及查詢是否證明索引是合理的。
  • 設計索引的最佳總體策略是使用與您將在生產中執行的資料集類似的資料集來分析各種索引配置,以檢視哪些配置效能最佳。檢查為集合建立的當前索引,以確保它們支援您當前和計劃的查詢。如果不再使用索引,請刪除索引。
  • 通常,MongoDB僅使用一個索引來完成大多數查詢。但是,$或查詢的每個子句可能使用不同的索引,從2.6開始,MongoDB可以使用多個索引的交集。

4.2 後續

  後續還會有MongonDB索引優化,副本集以及分片總結、最重要還會總結在使用MongoDB實戰以及實戰過程出現的一些坑,可關注後續更新MongoDB系列。

最後可關注公眾號,一起學習,每天會分享乾貨,還有學習視訊乾貨領取!

MongoDB系列--輕鬆應對面試中遇到的MongonDB索引(index)問題

相關文章