和大多數關係型數據庫同樣,MongoDB 支持使用索引來進行查詢優化,採用相似 B-Tree 的數據結構來儲存索引和文檔的位置信息,一樣也支持前綴索引和覆蓋索引。在當前最新的 MongoDB 4.0 中,索引的建立語法以下:git
db.collection.createIndex( <key and index type specification>, <options> )
複製代碼
<key and index type specification>
:用於指定創建索引的字段和升降序等屬性;<options>
:可選配置,一般用於指定索引的性質。爲方便後面的演示,這裏先插入部分測試數據,並針對 name 字段建立一個索引:github
db.user.insertMany([
{
name: "heibai",
age: 26,
birthday: new Date(1998,08,23),
createTime: new Timestamp(),
Hobby: ["basketball", "football", "tennis"]
},
{
name: "hei",
age: 32,
birthday: new Date(1989,08,23),
createTime: new Timestamp(),
Hobby: ["basketball", "tennis"]
},
{
name: "ying",
age: 46,
birthday: new Date(1978,08,23),
createTime: new Timestamp(),
Hobby: ["tennis"]
}
])
# 建立索引, -1表示以降序的順序存儲索引
db.user.createIndex( { name: -1 } )
複製代碼
建立索引後可使用 getIndexes()
查看集合的全部索引信息,示例以下:mongodb
db.user.getIndexes()
複製代碼
從輸出中能夠看到默認的索引名爲:字段名+排序規則。這裏除了咱們爲 name 字段建立的索引外,集合中還有一個 _id
字段的索引,這是程序自動建立的,用於禁止插入相同 _id
的文檔:shell
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "test.user"
},
{
"v" : 2,
"key" : {
"name" : -1
},
"name" : "name_-1",
"ns" : "test.user"
}
複製代碼
當前 MongoDB 4.x 支持如下六種類型的索引:數據庫
支持爲單個字段創建索引,這是最基本的索引形式,上面咱們針對 name 字段建立的索引就是一個單字段索引。須要特別說明的是,在爲 name 字段建立索引時,咱們爲其指定了排序規則。但實際上,在涉及單字段索引的排序查詢中,索引鍵的排序規則是可有可無,由於 MongoDB 支持在任一方向上遍歷索引。即如下兩個查詢均可以使用 name_-1
索引進行排序:json
db.user.find({}).sort({name:-1})
db.user.find({}).sort({name:1})
複製代碼
當前大多數數據庫都支持雙向遍歷索引,這和存儲結構有關 (以下圖)。在 B-Tree 結構的葉子節點上,存儲了索引鍵的值及其對應文檔的位置信息,而每一個葉子節點間則相似於雙向鏈表,既能夠從前日後遍歷,也能夠從後往前遍歷:數組
支持爲多個字段建立索引,示例以下:bash
db.user.createIndex( { name: -1,birthday: 1} )
複製代碼
須要注意的是 MongoDB 的複合索引具有前綴索引的特徵,即若是你建立了索引 { a:1, b: 1, c: 1, d: 1 }
,那麼等價於在該集合上還存在瞭如下三個索引,這三個隱式索引一樣能夠用於優化查詢和排序操做:數據結構
{ a: 1 }
{ a: 1, b: 1 }
{ a: 1, b: 1, c: 1 }
複製代碼
因此應該儘可能避免建立冗餘的索引,冗餘索引會致使額外的性能開銷。即若是你建立了索引 { name: -1, birthday: 1}
,那麼再建立 {name:-1}
索引,就屬於冗餘建立。性能
對於複合索引還須要注意它在排序上的限制,例如索引 {a:1, b:-1}
支持 {a:1, b:-1}
和 {a:-1, b:1}
形式的排序查詢,但不支持 {a: - 1, b:-1}
或 {a:1, b:1}
的排序查詢。即字段的排序規則要麼與索引鍵的排序規則徹底相同,要麼徹底相反,此時才能進行雙向遍歷查找。
若是索引包含類型爲數組的字段,MongoDB 會自動爲數組中的每一個元素建立單獨的索引條目,這就是多鍵索引。MongoDB 使用多鍵索引來優化查詢存儲在數組中的內容。建立示例以下:
db.user.createIndex( { Hobby: 1 } )
複製代碼
爲了支持基於哈希分片,MongoDB 提供了哈希索引,經過對索引值進行哈希運算而後計算出所處的分片位置。語法以下:
db.collection.createIndex( { _id: "hashed" } )
複製代碼
採用哈希運算獲得的結果值會比較分散, 因此哈希索引不能用於範圍查詢,只能用於等值查詢。
爲了支持對地理空間座標數據的有效查詢,MongoDB提供了兩個特殊索引:
這些數據一般是用於解決實際的地理查詢,如附近的美食、查詢範圍內全部商家等功能。其建立語法以下:
db.<collection>.createIndex( { <location field> : "2d" ,
<additional field> : <value> } ,
{ <index-specification options> } )
db.collection.createIndex( { <location field> : "2dsphere" } )
複製代碼
MongoDB 支持全文本索引,用於對指定字段的內容進行全文檢索。其建立語法以下:
db.<collection>.createIndex( { field: "text" } )
複製代碼
須要注意的是一個集合最多能夠有一個文本索引,但一個文本索引能夠包含多個字段,語法以下:
db.<collection>.createIndex(
{
field0: "text",
field1: "text"
}
)
複製代碼
建立文本索引是一個很是昂貴的操做,由於建立文本索引時須要對文本進行語義分析和有效拆分,還須要將拆分後的關鍵詞存儲在內存中,這對設備的運算能力和存儲空間都有很是高的要求,同時也會下降 MongoDB 的性能,因此須要謹慎使用。
建立索引時,能夠傳入第二個參數 <options>
用於指定索引的性質,經常使用的索引性質以下:
惟一索引能夠確保在同一個集合中惟一索引列的值只出現一次。 示例以下:
db.user.createIndex( { name: -1,birthday: 1}, { unique: true })
複製代碼
此時再執行下面的操做就會報錯,由於 name = heibai
而且 birthday = new Date(1998,08,23)
的數據已經存在:
db.user.insertOne({
name: "heibai",
birthday: new Date(1998,08,23)
})
複製代碼
上面這種狀況比較明顯,可是若是你執行下面這個操做兩次,你會發現只有第一次可以插入成功,第二個就會報 duplicate key 異常。這是由於在惟一索引的約束下,name 不存在的這種狀態也會被當作一種惟一狀態:
db.user.insertOne({
age: 12
})
複製代碼
想要解決這個問題,就須要用到索引的稀疏性。
爲了解決上面的問題,咱們須要爲索引添加稀疏性。因爲索引不能修改,因此只能先將上面的索引先刪除,而後再建立,併爲其指定 sparse
屬性爲 true,具體的建立語句以下:
db.user.dropIndex("name_-1_birthday_1")
db.user.createIndex( { name: -1,birthday: 1}, { unique: true,sparse: true})
複製代碼
此時你再屢次執行上面的插入語句就能插入成功。緣由是對於稀疏索引而言,它僅包含具備索引字段的文檔的索引信息,即便索引字段的值爲 null 也能夠,但不能缺乏相應的索引字段。若是缺乏,則相應的文檔就不會被包含在索引信息中。
部分索引主要用於爲符合條件的部分數據建立索引,它必須與 partialFilterExpression
選項一塊兒使用。 partialFilterExpression
選項可使用如下表達式來肯定數據範圍:
字段: 值
或使用 $eq 運算符);$exists: true
表達式;使用示例以下:
db.user.createIndex(
{ name: -1 },
{ partialFilterExpression: { age: { $gt: 30 } } }
)
複製代碼
TTL 索引容許爲每一個文檔設置一個超時時間,當一個文檔達到超時時間後,就會被刪除。TTL索引的到期時間等於索引字段的值 + 指定的秒數,這裏的索引字段的值只能是 Date 類型,示例以下:
db.user.createIndex( { "birthday": 1 }, { expireAfterSeconds: 60 } )
複製代碼
這裏咱們在 birthday 字段上創建 TTL 索引只是用於演示,實際上 TTL 索引主要是用於那些只須要在特定時間內保存的數據,如會話狀態、臨時日誌等。在使用 TTL 索引時,還有如下事項須要注意:
刪除索引的語法比較簡單,只須要調用 dropIndex
方法,能夠傳入索引的名稱也能夠傳入索引的定義,示例以下:
db.user.dropIndex("name_-1")
db.user.dropIndex({ name: -1,birthday: 1})
複製代碼
若是想要刪除所有的索引,則能夠調用 dropIndexes
方法,須要注意的是創建在 _id
上的默認索引是不會被刪除的。
db.collection.dropIndexes()
複製代碼
另外這個命令會獲取對應數據庫的寫鎖,並會阻塞其餘操做,直到索引刪除完成。
MongoDB 的 explain()
方法和 MySQL 的 explain 關鍵字同樣,都是用於顯示執行計劃的相關信息。示例以下:
db.user.find({name:"heibai"},{name:1,age:1}).sort({ name:1}).explain()
複製代碼
此時執行計劃的部分輸出以下:
"inputStage" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"name" : -1,
"birthday" : 1
},
"indexName" : "name_-1_birthday_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : [ ],
"birthday" : [ ]
},
"isUnique" : true,
"isSparse" : true,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "backward",
"indexBounds" : {
"name" : [
"[\"heibai\", \"heibai\"]"
],
"birthday" : [
"[MaxKey, MinKey]"
]
}
}
}
複製代碼
輸出結果中內層的 inputStage.stage
的值爲 IXSCAN
,表明此時用到了索引進行掃描,而且 indexName
字段顯示了對應的索引爲 name_-1_birthday_1
。而外層 inputStage.stage
的值爲 FETCH
,表明除了從索引上獲取數據外,還須要去對應的文檔上獲取數據,由於 age 信息並不存儲在索引上。這個輸出能夠證實 MongoDB 是支持前綴索引的,且單鍵索引支持雙向掃描。
這裏咱們對上面的查詢語句略作修改,不返回 age 字段和默認的 _id
字段,語句以下:
db.user.find({name:"heibai"},{_id:0, name:1}).sort({ name:1 }).explain()
複製代碼
此時輸出結果以下。能夠看到該查詢少了一個 FETCH
階段。表明此時只須要掃描索引就能夠獲取到所需的所有信息,這種狀況下 name_-1_birthday_1
索引就是這一次查詢操做的覆蓋索引。
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"name" : -1,
"birthday" : 1
},
"indexName" : "name_-1_birthday_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : [ ],
"birthday" : [ ]
},
"isUnique" : true,
"isSparse" : true,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "backward",
"indexBounds" : {
"name" : [
"[\"heibai\", \"heibai\"]"
],
"birthday" : [
"[MaxKey, MinKey]"
]
}
}
複製代碼
更多文章,歡迎訪問 [全棧工程師手冊] ,GitHub 地址:github.com/heibaiying/…