MongoDB系列四(索引).
一、索引簡(jiǎn)介
? ? 再來老生常談一番,什么是索引呢?數(shù)據(jù)庫(kù)索引與書籍的索引類似。有了索引就不需要翻整本書,數(shù)據(jù)庫(kù)可以直接在索引中查找,在索引中找到條目以后,就可以直接跳轉(zhuǎn)到目標(biāo)文檔的位置,這能使查找速度提高幾個(gè)數(shù)量級(jí)。
? ??然而,使用索引是有代價(jià)的:對(duì)于添加的每一個(gè)索引,每次寫操作(插入、更新、刪除)都將耗費(fèi)更多的時(shí)間。這是因?yàn)?#xff0c;當(dāng)數(shù)據(jù)發(fā)生變動(dòng)時(shí),MongoDB不僅要更新文檔,還要更新集合上的所有索引。因此,MongoDB限制每個(gè)集合上最多只能有64個(gè)索引。通常,在一個(gè)特定的集合上,不應(yīng)該擁有兩個(gè)以上的索引。于是,挑選合適的字段建立索引非常重要。
- 索引基數(shù)
基數(shù)(cardinality)就是集合中某個(gè)字段擁有不同值的數(shù)量。比如 gender 字段,基數(shù)一般就男女 2個(gè)而已;而像 mobile 這樣的字段,基數(shù)就會(huì)特別大。
通常來講,一個(gè)字段的基數(shù)越高,這個(gè)字段上的索引就越有用。這是因?yàn)樗饕軌蜓杆賹⑺阉鞣秶s小到一個(gè)比較小的結(jié)果集。對(duì)于低基數(shù)的字段,索引通常無法排除掉大量可能的匹配。假設(shè)我們?cè)?#34;gender"上有一個(gè)索引,需要查找名為Susan的女性用戶。通過這個(gè)索引,只能將搜索空間縮小到大約50%。
tips:在關(guān)系型數(shù)據(jù)庫(kù)中類似 gender 這樣的字段可以使用位圖索引。
- 索引原理淺析
我們以一個(gè)索引?{"age" : 1, "username" : 1} 來看看索引在MongoDB 中是如何存儲(chǔ)的,大致是這個(gè)樣子:
每一個(gè)索引條目都包含一個(gè)"age"字段 和 "username"字段,并且指向文檔在磁盤中的存儲(chǔ)位置。注意,這里的 age 嚴(yán)格的按照升序排序,并且相同的 age 對(duì)應(yīng)的 username 也嚴(yán)格的按照升序排序。
來看個(gè)例子 :db.users.find({"age" : 21}).sort({"username" : -1})
這個(gè)索引對(duì)于這個(gè)查詢來說是非常高效的,因?yàn)樗梢择R上定位到 age = 21 的位置,并且age = 21 中的 username 已經(jīng)是排序好的。
tips:排序方向并不重要:MongoDB可以在任意方向上對(duì)索引進(jìn)行遍歷。
tips:查詢中的字段順序無關(guān)緊要,MongoDB 會(huì)自動(dòng)找出可以使用索引的字段,而無視查詢的字段順序。
- $操作符如何使用索引
有一些查詢完全無法使用索引,也有一些查詢能夠比其他查詢更高效地使用索引。
$where:無法使用索引。
$nin:無法使用索引。
$exists:無法使用索引。因?yàn)樵谒饕?#xff0c;不存在的字段和null字段的存儲(chǔ)方式是一樣的,查詢必須遍歷每一個(gè)文檔檢查這個(gè)值是否真的為null還是根本不存在。
$ne:可以使用索引,但并不是很高效。因?yàn)楸仨毐闅v整個(gè)索引條目才能找到結(jié)果的文檔。
$not:能夠使用索引,但通常不知道如何使用索引,從而退化成全表掃描。
$or:能夠使用索引,但是$or 查詢會(huì)將 or 的條件拆分成多個(gè)獨(dú)立的查詢,然后再將結(jié)果合并在一起。這是很低效的,不建議用。建議用 $in 取代 $or 。
設(shè)計(jì)多鍵索引的時(shí)候要記得,要把基數(shù)大的字段放在索引的前面,因?yàn)檫@樣能更快縮小查詢的范圍。
二、索引類型
- 復(fù)合(組合)索引
復(fù)合索引就是一個(gè)建立在多個(gè)字段上的索引。
如果查詢中有多個(gè)排序方向或者查詢條件中有多個(gè)鍵,復(fù)合索引就非常有效。
db.userInfo.ensureIndex({"age":1,"age":1})?
進(jìn)行多鍵排序時(shí),索引的方向尤為重要。盡量做到多鍵排序的方向和復(fù)合索引的方向是一致的,因?yàn)檫@能很大的避免在內(nèi)存中進(jìn)行排序的運(yùn)算。
tips:相互反轉(zhuǎn)(在每個(gè)方向上都乘以-1)的索引是等價(jià)的:{"age" : 1, "user name" : -1}適用的查詢與{"age" : -1, "username" : 1}是完全一樣的。
復(fù)合索引具有雙重功能,而且對(duì)不同的查詢可以表現(xiàn)為不同的索引。如果有一個(gè){"age" :1, "username" : 1}索引,"age"字段會(huì)被自動(dòng)排序,就好像有一個(gè){"age" : 1}索引一樣。因此,這個(gè)復(fù)合索引可以當(dāng)作{"age" : 1}索引一樣使用。
- 唯一索引
唯一索引可以確保集合的每一個(gè)文檔的指定鍵都有唯一值。我們熟悉的 "_id" 索引就是一個(gè)唯一索引(但它不能被刪除,而其他唯一索引是可以刪除的)。
db.users.ensureIndex({"username" : 1}, {"unique" : true})
定義了唯一索引后,這個(gè)鍵就不允許插入重復(fù)的值了,否則會(huì)拋異常。
tips:A 字段不存在 和 A 字段為 null 是互斥的!
在已有的集合上創(chuàng)建唯一索引可能會(huì)報(bào)錯(cuò),因?yàn)榧现锌赡芤呀?jīng)有重復(fù)的值了。在極少數(shù)情況下,可能希望直接刪除重復(fù)的值。創(chuàng)建索引時(shí)使用"dropDups"選項(xiàng),如果遇到重復(fù)的值,第一個(gè)會(huì)被保留,之后的重復(fù)文檔都會(huì)被刪除。
db.users.ensureIndex({"username" : 1}, {"unique" : true, "dropDups" : true})
- 稀疏索引
在有些情況下,你可能希望唯一索引只對(duì)包含相應(yīng)鍵的文檔生效。如果有一個(gè)可能存在也可能不存在的字段,但是當(dāng)它存在時(shí),它必須是唯一的,這時(shí)就可以將unique和sparse選項(xiàng)組合在一起使用,創(chuàng)建唯一稀疏索引。注意:MongoDB中的稀疏索引(sparse index)與關(guān)系型數(shù)據(jù)庫(kù)中的稀疏索引是完全不同的概念。基本上來說,MongoDB中的稀疏索引只是不需要將每個(gè)文檔都作為索引條目。并且,稀疏索引并不一定是唯一的。
db.ensureIndex({"email" : 1}, {"unique" : true, "sparse" : true})
當(dāng)某個(gè)查詢使用了稀疏索引,就不會(huì)返回不包含這個(gè)字段的文檔。因?yàn)橄∈杷饕]有把每個(gè)文檔都作為索引條目。
- 覆蓋索引
如果你的查詢只需要查找索引中包含的字段,那就根本沒必要獲取實(shí)際的文檔。當(dāng)一個(gè)索引包含用戶請(qǐng)求的所有字段,可以認(rèn)為這個(gè)索引覆蓋了本次查詢。所以,盡可能使用投射篩選返回的字段,比如 {"_id":0,"age":1} 等,來實(shí)現(xiàn)覆蓋索引。
三、索引管理
- 新建索引
普通索引
db.userInfo.ensureIndex({"name":1},{"name","MyIndex"})
"1" 表示按照name進(jìn)行升序,"-1" 表示按照name進(jìn)行降序。
默認(rèn)的索引以 key1_1_key2_-1 這樣的方式命名,可以手動(dòng)指定索引的名字,如上。
對(duì)象索引
可以對(duì)整個(gè)對(duì)象建立索引,或者對(duì)對(duì)象的某個(gè)元素使用索引。
db.users.ensureIndex({"loc" : 1})
只有在進(jìn)行與對(duì)象字段順序完全匹配的子文檔查詢時(shí)(比如db.users.find({"loc" :{"ip" : "123.456.789.000", "city" : "Shelbyville", "state" :"NY"}}})),查詢優(yōu)化器才會(huì)使用"loc"上的索引。
db.users.ensureIndex({"loc.city" : 1})
有涉及到對(duì)象city的查詢都會(huì)使用這個(gè)索引。
數(shù)組索引
?對(duì)數(shù)組建立索引,實(shí)際上是對(duì)數(shù)組的每個(gè)元素建立一個(gè)索引條目。比如一個(gè)文檔中的數(shù)組字段有20個(gè)元素,那么該文檔就擁有了20個(gè)索引條目!所以對(duì)數(shù)組字段的索引建立要慎重。
- 刪除索引
db.userInfo.dropIndexes("name_1")
刪除指定索引
db.userInfo.dropIndexes()
刪除除了_id 以外的所有索引
- 操作索引
獲取當(dāng)前索引列表:db.userInfo.getIndexes()
hint 暴力選擇某種索引:db.userInfo.find({name:'zhangsan',birthday:'1989-3-2'}).hint({"name":1,"birthday":1})
強(qiáng)制使用全表掃描:db.userInfo.find({"birthday" : {"$lt" :"1989-3-2"}}).hint({"$natural" : 1})
索引分析函數(shù)explain:MongoDB 3.0前 和 MongoDB 3.0后存在很大的差異,這里只簡(jiǎn)單說明下,如果想詳細(xì)了解的話,可以關(guān)注該作者的文章:
MongoDB 3.0 前:db.driverLocation.find({"areaCode":"350203"}).explain()
cursor:表掃描方式 (basicCursor:順序查找)
nscanned:瀏覽了多少文檔
n:最終返回了幾個(gè)文檔
millis:總共耗時(shí)了多少毫秒
scanAndOrder:是否必須在內(nèi)存中對(duì)數(shù)據(jù)進(jìn)行排序
MongoDB 3.0 后:db.driverLocation.find({"areaCode":"350203"}).explain("executionStats")
executionTimeMillis:該query的整體查詢時(shí)間
nReturned:查詢返回的條目
totalKeysExamined:索引掃描條目
totalDocsExamined:文檔掃描條目?
轉(zhuǎn)載于:https://www.cnblogs.com/jmcui/p/8757299.html
總結(jié)
以上是生活随笔為你收集整理的MongoDB系列四(索引).的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Idea中Terminal中git基本操
- 下一篇: 2018 Multi-Universit