標籤:
Index 能夠提高查詢的效能,如果沒有Index,MongoDB必須掃描整個collection,從collection的第一個doc開始,直到最後一個doc,即使第一個doc之後的所有doc都不滿足查詢條件。如果在Collection上有合適的Index,例如,unique index,那麼MongoDB在按照index key尋找到一個doc之後,就不會繼續對其他doc查詢,極大地提高查詢效能。
MongoDB的 Index 結構跟關係型DB的NonClustered Index相似,都是BTree結構,在每個leaf node中,除了index key之外,還儲存相應doc在disk上的地址。在MongoDB中,沒有clustered index,因此,Collection初始的實體儲存體跟doc插入的順序有關,MongoDB按照doc插入的順序,依次將doc儲存在disk上,插入順序上相鄰的doc在disk的物理位置上也是相鄰的;對doc的修改可能對 collection 的實體儲存體發生變化,如果doc的修改不會導致doc的size增加,那麼doc會繼續儲存在原來的儲存空間中,而不會對collection的實體儲存體有影響,一旦修改操作導致doc的size增加,導致doc發生移動,那麼collection的實體儲存體就會發生變化。
一,doc的移動影響collection的實體儲存體
如果資料修改增加了doc的size,使其不能繼續存放在原來的儲存空間中,那麼MongoDB必須將其移動到collection的末尾,原先的儲存空間被閑置,導致doc的儲存密度下降,會嚴重影響查詢效能。doc的移動過程是非常慢的,相當於在一個原子操作中,先做doc的 delete 操作,後做doc的 insert 操作。
doc移動的過程如所示:
對doc B進行修改,使其Size增大,原先的位置不能容納B,MongoDB將B移動在Collection的末尾。原來的儲存空間被閑置。
二,建立index
MongoDB的index是BTree 結構,BTee結構的特點是:查詢每個值所要進行查詢的次數時固定的,最小的值儲存在最左邊的葉子節點上,做大的值儲存在最右邊的葉子節點上,
MongoDB預設按照“_id”欄位的升序建立index,最後建立的doc位於index的右側。如果每次查詢時,都是查詢最後的N個doc,那麼按照"_id"的值倒敘查詢,limit 前100,查詢效能是十分快速的。也可以手動建立符合業務需要的Index,MongoDB使用 db.collection.createIndex(keys,option)函數建立index。
keys的格式是:{field:1/-1,,},field是doc的欄位,1/-1 表示按照field排序的方向建立index:1表示按照field的升序建立,-1表示按照field的降序建立。
db.collection.createIndex(keys,option)
1,建立樣本資料,按照 age 欄位升序建立index
建立的Index按照age的升序儲存age欄位,在葉子節點中,除了age欄位,葉子節點還會儲存doc的地址(指標),用於定位相應的doc,查詢除index key(age)之外的其他欄位。下面的語句建立的index name是 age_1。
for(i=0;i<10000;i++){ db.foo.insert({"idx":i,name:"user "+1,age:i%90})}--create index by age ascendantdb.foo.createIndex({age:1})
2,查看查詢的query plan
在樣本中,由於查詢語句中沒有設定projection,MongoDB返回doc中的所有fields,由於index:age_1隻包含age欄位,其他欄位必須定位到原doc中擷取,因此多一次定址操作。
db.foo.find({age:22}).explain("executionStats")
在查詢語句中設定Projection,只返回age欄位,那麼index:age_1 就能包含結果集中所有欄位,不需要定位到原doc中,不僅提高查詢效能,而且減少Disk IO 和記憶體的使用量,因此,應對每個結果集設定Projection,不要返回"_id"欄位等其他不需要的欄位。通過搜尋index就能擷取所有field的index是覆蓋索引(convered index),覆蓋索引不需要定位到原doc中。
db.foo.find({age:22},{age:1,_id:0}).explain("executionStats")
三,index 和 排序
排序是一個非常耗費記憶體資源的操作,在MongoDB中,如果排序的中間結果集大小消耗的系統記憶體超過32MB,MongoDB就會報錯,拒絕如此多的資料進行排序。32MB是一個閾值,如果超出值,那麼必須使用 index 來擷取經過排序的資料集。
MongoDB:When unable to obtain the sort order from an index, MongoDB will sort the results in memory, which requires that the result set being sorted is less than 32 megabytes. When the sort operation consumes more than 32 megabytes, MongoDB returns an error.
通過Index來執行排序操作,要求排序的欄位和index key的首碼欄位相同,如果滿足該條件,那麼MongoDB會直接返回順序的結果集,而不需要執行實際的排序操作。例如,如果index key是{age:1,name:1},如果排序操作是sort({age:1}),或 sort({age:1,name:1}),符合index首碼排序,那麼結果集會直接返回,不需要排序;如果排序操作是sort({name:1}),或sort({name:1,age:1}),不符合index首碼排序,結果集還是需要在記憶體中進行排序,如果記憶體消耗超過32MB,MongoDB報錯。
樣本,符合首碼排序的Index 和 sort 操作
db.foo.find({age:22}).sort({age:1})
由於MongoDB通過Index來執行排序操作,並且MongoDB對排序操作消耗的記憶體資源有嚴格限制,因此,在建立index,應在尋找和排序之間做折衷,在滿足排序操作的前提下,使查詢效能更高。在建立index時,使用 {"Sort Key":1, "query filter":1} 格式是非常有用。
四,對內嵌doc進行索引
MongoDB index的強大之處在於能夠在內嵌doc的欄位上建立index,建立的文法和普通doc一致,在引用內嵌doc中的欄位時,使用dot notation,可以對任意深度的內嵌doc欄位建立index。
例如,doc結構如下,contact是內嵌doc,按照 contact.phone 欄位升序建立索引。
{name:"u1",age:22,contact: { phone:123 email:"[email protected]" }}--create index db.foo.createIndex({"contact.phone":1})
五,Index 維護
1,查看在collection上建立的index
使用db.collectionName.getIndexes() 查看給定collection上的所有index資訊:
- key是指index key的定義,包括兩部分:key 和排序的方向;
- name是index name;
- ns是namespace;
- v 標識index 版本,如果索引中包含"v":1,說明index是以新格式儲存的,效能較高。
db.collection.getIndexes()
2,刪除index
db.collection.dropIndex(index)
方式一,按照index name 刪除index
db.foo.dropIndex("age_1")
方式二,按照Index key刪除index
db.foo.dropIndex({age:1})
3,重建collection中的所有index
db.collection.reIndex()
The db.collection.reIndex() drops all indexes on a collection and recreates them. This operation may be expensive for collections that have a large amount of data and/or a large number of indexes.
參考doc:
MongoDB CRUD Concepts
cursor.explain()
Indexes
MongoDB 使用Index