1. 程式人生 > >MongoDB索引文件翻譯(一)

MongoDB索引文件翻譯(一)

索引

索引讓MongoDB有效的執行查詢語句,沒有索引MongoDB必須對collection中的每個文件掃描來選擇複合條件的條件的。如果對於一個查詢存在合適的索引,MongoDB能利用索引限制檢查的文件數。

索引是一種特殊的資料結構,以簡單的遍歷形式儲存資料集合中的一小部分。索引儲存特定欄位或者欄位的集合,以欄位的值排序。索引的排序支援高效的等值查詢和範圍查詢。除此之外,MongoDB能利用索引的排序返回排序後結果集。

預設的 _id 索引 

MongoDB在collection建立時為 _id 欄位建立唯一索引, _id 索引阻止客戶端插入 _id 值相同的文件。無法刪除 _id 欄位上的索引。

建立索引

db.collection.createIndex( { name: -1 } )

索引型別

單欄位索引

{ score:1 }

複合索引

{ userid: 1, score: -1 }

多鍵索引

db.collection.createIndex( { size.height : -1 } )

單欄位索引

示例文件資料如下:

{
“_id”: ObjectId(“570c04a4ad233577f97dc459”),
“score”: 1034,
“location”: { state: “NY”, city: “New York” }
}

對單個欄位建立降序索引

為score 建立索引

db.records.createIndex( { score: 1 } )

對內嵌欄位建立索引(使用符號“.”)

為location.state欄位建立索引

db.records.createIndex( { “location.state”: 1 } )

建立的索引支援以下查詢

db.records.find( { “location.state”: “CA” } )
db.records.find( { “location.city”: “Albany”, “location.state”: “NY” } )

為內嵌文件建立索引

db.records.createIndex( { location: 1 } )

如下查詢能用到location欄位的索引

db.records.find( { location: { city: “New York”, state: “NY” } } )

複合索引

示例文件:

{
“_id”: ObjectId(…),
“item”: “Banana”,
“category”: [“food”, “produce”, “grocery”],
“location”: “4th Street Store”,
“stock”: 4,
“type”: “cases”
}

建立複合索引

命令:

db.collection.createIndex( { : , : , … } )

為 item 以及 stock 建立索引:

db.products.createIndex( { “item”: 1, “stock”: 1 } )

複合索引中欄位順序是重點,上例中將先按照 item 的值對文件引用排序,然後對符合item 條件的文件按照 stock 的值排序。

除了支援所有索引欄位的查詢外,複合索引還支援字首匹配查詢。意思就是,{ “item”: 1, “stock”: 1 } 支援對單個 item 欄位的查詢,也支援對 item 和 stock 兩個欄位的查詢。

db.products.find( { item: “Banana” } )
db.products.find( { item: “Banana”, stock: { $gt: 5 } } )

排序

索引以升序(1) 或降序(-1)儲存對欄位的引用。對於單欄位索引,索引的順序無關緊要,因為MongoDB可在任一順序遍歷。但是對於複合索引,排序順序關乎到索引能否支援排序操作。

一個 collection 其文件欄位有 username 和 date 。如下索引:

db.events.createIndex( { “username” : 1, “date” : -1 } )

可以支援以下兩種查詢方式:

db.events.find().sort( { username: 1, date: -1 } )
db.events.find().sort( { username: -1, date: 1 } )

但不支援以下查詢方式:

db.events.find().sort( { username: 1, date: 1 } )

字首

索引字首是索引欄位開頭的子集,有如下複合索引:

{ “item”: 1, “location”: 1, “stock”: 1 }

則有如下索引字首:
- { item: 1 }
- { item: 1, location: 1 }

對於如下欄位的查詢可以用到索引:
- item 欄位
- item 欄位 和 location 欄位
- item 欄位 和 location 欄位 和 stock 欄位

如下查詢無法用到索引:
- location 欄位
- location 欄位,或者…
- location 欄位 和 stock 欄位

多鍵索引

建立多鍵索引

使用db.collection.createIndex() 建立索引:

db.coll.createIndex( { : < 1 or -1 > } )

若新增索引的欄位是一個數組,MongoDB 自動建立多鍵索引;不需要明確指定索引型別。

索引邊界

如果一個索引是多鍵索引,索引邊界的計算遵循特殊的規則。多鍵索引邊界的詳細資訊,參看 Multikey Index Bounds.

唯一多鍵索引

對於唯一索引適用於集合中的單獨文件,而不是單個文件之中。

由於唯一約束應用於單獨文件,對於唯一多鍵索引,只要文件的索引鍵值不重複其他文件的索引鍵值,則對於唯一的多鍵索引,文件可能具有導致重複索引鍵的陣列元素。

限制

複合多鍵索引

對於一個複合多鍵索引,每個建立過索引的文件最多有一個值是陣列的欄位能做索引。意思就是:
- 不能為多個值為陣列的欄位建立多鍵複合索引。例如:

{ _id: 1, a: [ 1, 2 ], b: [ 1, 2 ], category: “AB - both arrays” }

你不能建立一個複合多鍵索引{a:1, b:1},因為a、b欄位的值都是陣列

當一個複合多鍵索引已存在,你不能插入一個文件,違反此規則。
例如某collection有如下資料:

{ _id: 1, a: [1, 2], b: 1, category: “A array” }
{ _id: 2, a: 1, b: [1, 2], category: “B array” }

複合多鍵索引{a:1, b:1}是允許建立的,因為索引欄位中都只有一個欄位的值是陣列。
但是建立複合多鍵索引後,如果你想插入一個文件,a和b欄位的值都是陣列,那麼MongoDB會插入失敗。

如果一個欄位是文件陣列,可以為內嵌欄位新增索引來建立複合索引。
例如:

{ _id: 1, a: [ { x: 5, z: [ 1, 2 ] }, { z: [ 1, 2 ] } ] }   
{ _id: 2, a: [ { x: 5 }, { z: 4 } ] }

可以建立複合索引{“a.x”:1, “a.z”:1}。依然符合限制:最多一個欄位的值是陣列。

共享索引

不能指定多鍵索引,作為共享索引

雜湊索引

雜湊索引不能做多鍵索引

覆蓋查詢

多鍵索引不能覆蓋對陣列欄位的查詢

整體查詢陣列欄位

當查詢過濾器為整個陣列指定一個特定的匹配條件時,MongoDB能運用多鍵索引查詢陣列的第一個元素,但是不運用多鍵索引掃描查詢整個陣列。

例如:

{ _id: 5, type: “food”, item: “aaa”, ratings: [ 5, 8, 9 ] }
{ _id: 6, type: “food”, item: “bbb”, ratings: [ 5, 9 ] }
{ _id: 7, type: “food”, item: “ccc”, ratings: [ 9, 5, 8 ] }
{ _id: 8, type: “food”, item: “ddd”, ratings: [ 9, 5 ] }
{ _id: 9, type: “food”, item: “eee”, ratings: [ 5, 9, 5 ] }

在ratings欄位上有多鍵索引

db.inventory.createIndex( { ratings: 1 } )

如下查詢查詢ratings欄位為[5, 9] 的文件:

db.inventory.find( { ratings: [ 5, 9 ] } )

MongoDB能運用多鍵索引找到在ratings陣列任一位置有5的文件,然後MongoDB檢索過濾這些文件,找到ratings欄位等於[5, 9] 的文件。

例子

索引基本陣列

考慮survey collection 有如下文件:

{ _id: 1, item: “ABC”, ratings: [ 2, 5, 9 ] }

在ratings 欄位建立索引

db.survey.createIndex( { ratings:1 } )

因為ratings 欄位包含陣列在ratings上的索引為多鍵索引。該多鍵索引包含如下三個索引鍵,每個都指向相同的文件。

  • 2,
  • 5,and
  • 9,

索引內嵌文件的陣列

可為包含巢狀物件的陣列建立多鍵索引

考慮inventory collection 有如下文件:
>
{
_id: 1,
item: “abc”,
stock: [
{ size: “S”, color: “red”, quantity: 25 },
{ size: “S”, color: “blue”, quantity: 10 },
{ size: “M”, color: “blue”, quantity: 50 }
]
}

{
_id: 2,
item: “def”,
stock: [
{ size: “S”, color: “blue”, quantity: 20 },
{ size: “M”, color: “blue”, quantity: 5 },
{ size: “M”, color: “black”, quantity: 10 },
{ size: “L”, color: “red”, quantity: 2 }
]
}

{
_id: 3,
item: “ijk”,
stock: [
{ size: “M”, color: “blue”, quantity: 15 },
{ size: “L”, color: “blue”, quantity: 100 },
{ size: “L”, color: “red”, quantity: 25 }
]
}


>

建立索引:

db.inventory.createIndex( { “stock.size”: 1, “stock.quantity”: 1 } )

複合多鍵索引既支援兩個欄位的查詢也支援索引字首”stock.size”,例如:

db.inventory.find( { “stock.size”: “M” } )
db.inventory.find( { “stock.size”: “S”, “stock.quantity”: { $gt: 20 } } )

複合多鍵索引也能支援如下的示例:

db.inventory.find( ).sort( { “stock.size”: 1, “stock.quantity”: 1 } )
db.inventory.find( { “stock.size”: “M” } ).sort( { “stock.quantity”: 1 } )

多鍵索引邊界

索引邊界掃描定義在一個查詢中索引搜尋的部分。當索引上存在多個欄位,為了產生較小的掃描邊界,MongoDB會嘗試
通過求交集或者並集來獲取索引邊界。

多鍵索引交叉邊界

交叉邊界是多個邊界的邏輯連線。如給出兩個邊界[ [ 3, Infinity ] ] 和 [ [ -Infinity, 6 ] ] 邊界的交集為:[ [ 3, 6 ] ]。

給出一個索引陣列,考慮一個查詢指定該陣列的多個欄位,並且可以使用多鍵索引。如果一個$elemMatch 連線查詢欄位,MongoDB能交叉多鍵索引的邊界

比如:survey collection 包含一個欄位item 和一個數組欄位ratings:

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

在ratings建立一個多鍵索引:

db.survey.createIndex( { ratings: 1 } )

如下使用$elemMatch 來查詢陣列只要包含一個元素符合兩個條件

db.survey.find( { ratings : { $elemMatch: { gte:3,lte: 6 } } } )

分別考慮查詢欄位:
- 大於或者等於3的邊界是[ [ 3, Infinity ] ];
- 小於或等於6的邊界是[ [ -Infinity, 6 ] ];

因為查詢語句使用$elemMatch 連線查詢謂詞,MongoDB能交叉邊界:

ratings: [ [ 3, 6 ] ]

如果查詢語句不使用$elemMatch連線條件,MongoDB不能交叉多鍵索引的邊界,考慮如下查詢:

db.survey.find( { ratings : { gte:3,lte: 6 } } )

查詢語句查詢ratings陣列至少有一個大於等於3且小於等於6的元素,因為單個元素不需要
滿足兩個標準,MongoDB不會交叉邊界,而是使用 [ [ 3, Infinity ] ] 或 [ [ -Infinity, 6 ] ]。MongoDB不保證會使用哪一個邊界。

多鍵索引的複合邊界

複合邊界指為複合索引的多個鍵使用邊界,比如給出一個複合索引{ a:1, b:1 } ,a欄位的邊界[ [ 3, Infinity ] ],b欄位的邊界[ [ -Infinity, 6 ] ] 複合邊界導致兩個邊界都使用:

{ a: [ [ 3, Infinity ] ], b: [ [ -Infinity, 6 ] ] }

如果MongoDB不能複合兩個邊界,MongoDB總是通過前面欄位的邊界約束索引掃描,在本例中就是a: [ [ 3, Infinity ] ]。

陣列欄位上的複合索引

考慮複合多鍵索引,一個複合索引其中一個索引欄位的值是陣列。例如一個collection survey 的文件中包含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 } )

以下查詢指定索引的兩個鍵:

db.survey.find( { item: “XYZ”, ratings: { $gte: 3 } } )

分開考慮查詢欄位:
item: “XYZ” 的邊界是[ [ “XYZ”, “XYZ” ] ];
ratings: { $gte: 3 } 的邊界是 [ [ 3, Infinity ] ];

MongoDB能夠複合兩個邊界使用聯合邊界:

{ item: [ [ “XYZ”, “XYZ” ] ], ratings: [ [ 3, Infinity ] ] }

標量索引欄位上的範圍查詢

從MongoDB3.4開始,MongoDB跟蹤哪些欄位會使得索引成為多鍵索引。跟蹤這個資訊允許MongoDB查詢引擎使用更加鬆散的邊界。

前面提到的複合索引是在標量欄位item和陣列欄位ratings:

db.survey.createIndex( { item: 1, ratings: 1 } )

對於WiredTiger 和In-Memory 儲存引擎如果一個查詢操作指定複合多鍵索引(大於等於3.4版本建立)的一個標量欄位,MongoDB會交叉欄位的邊界。

例如,如下查詢中指定一個標量欄位的查詢範圍和一個數組欄位的查詢範圍。

db.survey.find( {
item: { gte:"L",lte: “Z”}, ratings : { $elemMatch: { gte:3,lte: 6 } }
} )

>

MongoDB會交叉邊界,對於item來說就是[ [ “L”, “Z” ] ] 而對於ratings是 [[3.0, 6.0]]:

“item” : [ [ “L”, “Z” ] ], “ratings” : [ [3.0, 6.0] ]

對於另一個例子,考慮標量欄位是內嵌的,比如:

{ _id: 1, item: { name: “ABC”, manufactured: 2016 }, ratings: [ 2, 9 ] }
{ _id: 2, item: { name: “XYZ”, manufactured: 2013 }, ratings: [ 4, 3 ] }

為”item.name”,”item.manufactured”和陣列欄位ratings:

db.survey.createIndex( { “item.name”: 1, “item.manufactured”: 1, ratings: 1 } )

對於如下查詢:

db.survey.find( {
“item.name”: “L” ,
“item.manufactured”: 2012
} )

MongoDB使用複合邊界:

“item.name” : [ [“L”, “L”] ], “item.manufactured” : [ [2012.0, 2012.0] ]

更早一些的版本無法複合這些標量欄位的邊界

標量欄位的範圍查詢(MMAPv1)

對於MMAPv1儲存引擎,MongoDB不能複合多鍵索引中標量欄位的邊界,即使僅僅是查詢標量欄位。

內嵌文件陣列中欄位的複合索引

如果一個數組包含內嵌文件,為了給包含內嵌文件的欄位加索引,使用”.欄位名”的方式,比如給出如下內嵌文件:

ratings: [ { score: 2, by: “mn” }, { score: 9, by: “anon” } ]

“.欄位名”的方式對於score來說就是”ratings.score”

非陣列欄位和陣列中欄位的複合邊界

考慮collection survey2中的如下文件:

{
_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的邊界,MongoDB不保證的選用哪一個欄位的邊界,例如MongoDB複合item和ratings.score的邊界:

{
“item” : [ [ “XYZ”, “XYZ” ] ],
“ratings.score” : [ [ -Infinity, 5 ] ],
“ratings.by” : [ [ MinKey, MaxKey ] ]
}

或者MongoDB也可能選擇複合item和ratings.by的邊界:

{
“item” : [ [ “XYZ”, “XYZ” ] ],
“ratings.score” : [ [ MinKey, MaxKey ] ],
“ratings.by” : [ [ “anon”, “anon” ] ]
}

但是複合ratings.score和ratings.by的邊界,查詢語句必須使用$elemMatch

 陣列中欄位的複合索引邊界

為了複合同一陣列中的鍵的邊界:
- 索引鍵必須共享相同的欄位路徑,查詢必須在路徑的上欄位指定使用$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路徑,如下查詢在ratings上使用$elemMatch來查詢至少有一個元素複合兩個條件的文件:

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” ] ] }

沒有$elemMatch的查詢

如果查詢不在索引陣列的條件中加入$elemMatch,MongoDB不能複合邊界。考慮如下查詢:

db.survey2.find( { “ratings.score”: { $lte: 5 }, “ratings.by”: “anon” } )

因為陣列中的單個內嵌文件不需要複合兩個標準,MongoDB不會複合邊界。當使用一個複合索引,如果MongoDB不能約束素有的索引欄位,MongoDB總是約束索引中開頭欄位,本例總就是”ratings.score”

{
“ratings.score”: [ [ -Infinity, 5 ] ],
“ratings.by”: [ [ MinKey, MaxKey ] ]
}

$elemMatch 在不完整路徑上
如果查詢不在內嵌欄位的路徑上指定$elemMatch,MongoDB不能複合來自同一陣列的索引欄位的邊界

例如:collection survey3包含文件:

{
_id: 1,
item: “ABC”,
ratings: [ { scores: [ { q1: 2, q2: 4 }, { q1: 3, q2: 8 } ], loc: “A” },
{ scores: [ { q1: 2, q2: 5 } ], loc: “B” } ]
}
{
_id: 2,
item: “XYZ”,
ratings: [ { scores: [ { q1: 7 }, { q1: 2, q2: 8 } ], loc: “B” } ]
}

建立索引:

db.survey3.createIndex( { “ratings.scores.q1”: 1, “ratings.scores.q2”: 1 } )

欄位”ratings.scores.q1”和 “ratings.scores.q2”共享欄位路徑”ratings.scores” $elemMatch必須在該路徑上。

如下查詢使用了$elemMatch但是不在需要的路徑上:

db.survey3.find( { ratings: { $elemMatch: { ‘scores.q1’: 2, ‘scores.q2’: 8 } } } )

像這樣的情況MongoDB不能複合邊界,”ratings.scores.q2”欄位在索引掃描時不能被約束,為了複合邊界,查詢必須在”ratings.scores”路徑上使用$elemMatch:

db.survey3.find( { ‘ratings.scores’: { $elemMatch: { ‘q1’: 2, ‘q2’: 8 } } } )