《一起學mongodb》之第四卷 索引
前言
索引的重要性在數據庫中是不言而喻的,mysql 中使用了 B+ 數來當做索引的數據結構,為 mysql 性能提升做了很大的貢獻,那么在 mongoDB 中又使用了什么數據結構呢?今天就和大家聊聊 mongoDB 的索引
- mongoDB 的索引數據結構是什么?
- mongoDB 支持哪些索引類型?
- 索引奇淫技巧 ?
- 怎么查看我到有沒有用到索引?
mongo 的索引數據結構是什么
網上對 mongoDB 的數據結構有很多種說法,有說 B- 樹的,有說 B 樹的,還有說 B+ 樹的
這里先說一個常識性的誤區,「沒有 B 減樹」,B-tree 其實就是 B 樹,中間的破折號只是用來連接而已,「只有 B 樹和 B+ 樹」
官方文檔明確說到,在 WiredTiger 存儲引擎當中,可以支持 B-Tree 和 LSM 兩種結構組織數據,「默認使用 B+ 樹」的數據結構在內存中維護表的數據,說 B 樹也沒錯,因為 B+ 樹就是 B 樹的子集
對于 WiredTiger 存儲引擎來說,集合所在的數據文件和相應的索引文件都是按 B-Tree 結構來組織的,不同之處在于數據文件對應的 B 樹葉子結點上除了存儲鍵名外(keys),還會存儲真正的集合數據(values),所以數據文件的存儲結構也可以認為是一種 B+Tree
mongo 中支持哪些索引類型
單個索引
簡而言之就是單個字段的索引,比如
db.children.createIndex({ age : 1 })
就相當于給 children 表的 age 字段建立了一個升序索引 (升序 ( 1) 或降序 ( -1) )
復合索引
符合索引其實就是多個字段自合成一個索引,比如
db.children.createIndex({ age : 1,height : 1 })
就相當于給 children 表 以 age 字段升序 height 字段升序建立了一個索引
多鍵索引
在MongoDB中可以「基于數組來創建索引」。MongoDB為數組每一個元素創建索引值。多鍵索引支持數組字段的高效查詢,比如
([{ _id: 1, name: "xiaohong", age: "1", ratings: [ 1, 2, 3 ] })
db.children.createIndex( { ratings: 1 } )
但是對于一個復合多鍵索引,「每個索引最多可以包含一個數組」。比如以下情況就無法建立索引
([{ _id: 1, name: "xiaohong", age: "1", ratings: [ 1, 2, 3 ],teams:[ 1 , 3 , 4] })
db.children.createIndex( { ratings: 1 ,teams : -1} )
地理空間索引
為了支持對地理空間坐標數據的高效查詢,MongoDB提供了兩個特殊的索引:在返回結果時使用平面幾何的2d索引和使用球面幾何返回結果的2dsphere索引。有關地理空間索引的高級介紹,請參見2d Index Internals。
文本索引
MongoDB提供了一種文本索引類型,它支持搜索集合中的字符串內容。這些文本索引不存儲特定于語言的停止詞(例如**“the”,“a”,“or”**),并且在一個集合中只存儲根詞的詞干。有關文本索引和搜索的更多信息,請參見文本索引。
Hashed索引
為了支持基于Hashed的分片,MongoDB提供了Hashed索引類型,該索引類型對字段值的Hashed進行索引。這些索引在其范圍內具有更隨機的值分布,但只支持相等匹配,而不支持基于范圍的查詢。
索引特性
唯一索引
在創建集合期間,MongoDB 在_id字段上創建唯一索引,這也是默認的唯一索引。該索引主要是為了區分文檔并且不能刪除。創建方式就是加上 unique: true
db.children.createIndex( { age : 1 }, { unique: true } )
部分索引
部分索引僅索引集合中符合指定過濾器表達式的文檔。
比如 children 表中,將 age 大于 5 數據創建一個升序索引
db.children.createIndex(
{age:1},
{partialFilterExpression: {age: {$gt:5}}})
建立部分索引可以節省存儲空間,提升索引查詢效率。比如該文檔 2000 年前的數據為垃圾數據,不常用,那就可以根據時間大于 2000 年創建索引
稀疏索引
索引的稀疏屬性可確保索引僅包含具有索引字段的文檔的條目。索引會跳過沒有索引字段的文檔。創建方式就是加上 sparse: true
db.children.createIndex( { "age": 1 }, { sparse: true } )
TTL索引
TTL 索引是 MongoDB 可以使用的特殊索引,它可以在一定時間后自動從集合中刪除文檔。
db.children.createIndex( { "lastModifiedDate": 1 }, { expireAfterSeconds: 5 } )
以上案例就是設置 5 秒后過去,使用方式只需要創建索引時加上 expireAfterSeconds: 5
覆蓋索引
所有需要查詢的數據都在索引當中,不需要從數據頁中再去尋找數據
比如我此時為 children 表的時間創建了一個索引
db.children.createIndex({ age : 1 })
在此時我查找年齡為兩歲的孩子時,就不需要從數據頁中去尋找數據了
db.children.find({ age : 2 })
前綴索引
所有的前綴索引都可以被這條索引所覆蓋,不需要再去針對這些前綴建立額外的索引,避免額外的開銷
比如我此時為 children 表的時間創建了「一個復合索引(多字段索引)」
db.children.createIndex({ age : 1,name : 1,address : 1})
「那么其實這條索引等價于三條索引」,分別是
db.children.createIndex({ age : 1 })
db.children.createIndex({ age : 1,name : 1 })
db.children.createIndex({ age : 1,name : 1,address : 1})
使用索引的奇淫技巧
組合索引的最佳方式 ESR 原則
- 1.精準匹配(Equal)的放前面
- 2.排序(Sort)的放中間
- 3.范圍匹配(Range)的方最后
比如一條查詢語句
db.largeClass.find({className:"a",age:{$gte:5}}).sort(time:1)
最好的索引建立就應該是 {className:1,time:1,age:1}
E 放在最前面大家應該都能理解,用等值匹配去過濾掉大量數據,「那為什么是 ESR 不是 ERS 呢?」
原因就是因為如果范圍匹配放在中間,那么后續我們排序的時候只能進行「內存排序」,而內存排序又是很消耗資源的,數據量大時可能會「面對著多次的磁盤讀取刷內存操作」,非常的消耗時間
合理使用部分索引
對于有些比較大的文檔,可能很多數據都是無用的,比如文檔中有三年的數據,但是業務只需要最近一年的數據,那么就可以只根據時間對最近一年的數據建立索引
后臺創建索引
記得在創建索引時加上 {background: true},在后臺創建索引,防止影響 mongoDB 的正常工作,讓其自動調配創建時間
怎么查看我到有沒有用到索引?
在 mongoDB 中提供了 「explain 執行計劃」,可以清晰的看到你當前的查詢語句時候有使用到索引,使用方式也很簡單,只要在查詢語句右面加上 .explain 就可以了,有幾個「比較重要的屬性」在這里說下
「executionTimeMillis」:指的是我們這條語句的執行時間
「docsExamined」:文檔掃描數
「totalDocsExamined」:文檔掃描條目
「totalKeysExamined」:索引掃描條目
「stage」:掃描類型,主要有
COLLSCAN:全表掃描
IXSCAN:索引掃描
FETCH:根據索引去檢索指定document
SHARD_MERGE:將各個分片返回數據進行merge
SORT:表明在內存中進行了排序
LIMIT:使用limit限制返回數
SKIP:使用skip進行跳過
IDHACK:針對_id進行查詢
SHARDING_FILTER:通過mongos對分片數據進行查詢
COUNT:利用db.coll.explain().count()之類進行count運算
COUNTSCAN:count不使用Index進行count時的stage返回
COUNT_SCAN:count使用了Index進行count時的stage返回
SUBPLA:未使用到索引的$or查詢的stage返回
TEXT:使用全文索引進行查詢時候的stage返回
PROJECTION:限定返回字段時候stage的返回
所以當 「stage 為 IXSCAN」 的時候就是使用到了索引掃描