上篇我們學(xué)習(xí)了mongoDB的文檔相關(guān)操作,了解了mongo的查詢機(jī)制柴钻,以及支持的幾種常見查詢方式杜顺,本篇我們從應(yīng)用的角度學(xué)習(xí)mongoDB中的索引機(jī)制
我們知道如果沒有索引,MongoDB在讀取數(shù)據(jù)時(shí)必須掃描集合中的每個(gè)文件并選取那些符合查詢條件的記錄个束。這種掃描全集合的查詢效率是非常低的,特別在處理大量的數(shù)據(jù)時(shí)聊疲,查詢可以要花費(fèi)幾十秒甚至幾分鐘茬底,這是任何網(wǎng)站都無法接受的,而索引通常能夠極大的提高查詢的效率获洲,所以索引機(jī)制成為了必不可少的一部分阱表。
索引說明
如果是熟悉關(guān)系型數(shù)據(jù)庫開發(fā),例如mysql贡珊,都知道在數(shù)據(jù)庫查詢的時(shí)候我們可以使用explain
來查看數(shù)據(jù)庫語句的執(zhí)行情況最爬, 可以看到掃描的行數(shù),索引的選擇以及預(yù)計(jì)的時(shí)間等门岔,而在mongoDB中也存在explain
函數(shù)爱致,可以幫助我們查看mongoDB索引的執(zhí)行過程,如下:
db.set.find().explain()
可以看到輸出了mongoDB的執(zhí)行計(jì)劃:
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "set.set",
"indexFilterSet" : false,
"parsedQuery" : {
},
"queryHash" : "8B3D4AB8",
"planCacheKey" : "8B3D4AB8",
"winningPlan" : {
"stage" : "COLLSCAN",
"direction" : "forward"
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "localhost.localdomain",
"port" : 27017,
"version" : "4.2.8",
"gitVersion" : "43d25964249164d76d5e04dd6cf38f6111e21f5f"
},
"ok" : 1
}
粗略一看輸出的信息很多寒随,我們暫時(shí)只需要看幾個(gè)信息糠悯,如queryPlanner
屬性代表查詢計(jì)劃的內(nèi)容帮坚,serverInfo
代表當(dāng)前mongo客戶端的信息。如果是mongoDB3.2及以前的版本的話互艾,我們使用explain
得到的結(jié)果和當(dāng)前是不一樣的试和,大致如下:
{
"cursor" : "BasicCursor",
"nscanned" : 102,
"nscannedObjects" : 102,
"n" : 1,
"millis" : 2,
"nYields" : 0,
"nChunkSkips" : 0,
"isMultiKey" : false,
"indexOnly" : false,
"indexBounds" : {
}
}
而這里的話,我們可以關(guān)注 nscanned 屬性纫普,這個(gè)屬性代表當(dāng)前掃描的行數(shù)方淤,而 millis 屬性則代表了這個(gè)查詢大概需要的毫秒數(shù),除此之外還有個(gè) n 屬性文虏,這個(gè)屬性代表著查詢返回的結(jié)果條數(shù)腕让。了解了這些后,我們來給url上添加一個(gè)索引試試悦昵,添加索引需要使用createIndex
函數(shù)來完成:
db.set.createIndex({"url":1})
這里需要注意肴茄,如果是mongoDB3.2及以前的版本,創(chuàng)建索引的函數(shù)則是ensureIndex
但指,而在mongoDB3.2以上版本中寡痰,修改為了createIndex
函數(shù),不過如果我們使用ensureIndex
函數(shù)依然可以創(chuàng)建索引棋凳,只是ensureIndex
函數(shù)成為了 createIndex()
的別名拦坠。
接著我們來看看 createIndex
函數(shù)中的參數(shù),第一個(gè)參數(shù)是需要設(shè)置索引的鍵剩岳,而第二個(gè)參數(shù)與以往查詢函數(shù)中的1不同贞滨,這里的1則是代表了索引按照升序進(jìn)行創(chuàng)建,而-1則是代表按照降序進(jìn)行創(chuàng)建拍棕。
在調(diào)用了createIndex
函數(shù)后晓铆,如果該集合數(shù)據(jù)較多的話,我們會(huì)發(fā)現(xiàn)此命令會(huì)阻塞一段時(shí)間绰播,由于每個(gè)機(jī)器性能不一樣骄噪,如果我們想要查詢當(dāng)前創(chuàng)建索引的進(jìn)度,可以選擇在開啟一個(gè)客戶端連接窗口蠢箩,使用如下方式查詢當(dāng)前創(chuàng)建索引的進(jìn)度链蕊,或者選擇讀取mongoDB日志的方式查看:
db.currentOp() //查看數(shù)據(jù)庫創(chuàng)建索引進(jìn)度
在索引創(chuàng)建完畢后,我們再次使用url屬性進(jìn)行文檔查詢和掃描操作谬泌,這個(gè)時(shí)候我們會(huì)發(fā)現(xiàn)滔韵,效率明顯提升,幾乎在瞬間就返回了查詢的結(jié)果掌实。然而陪蜻,索引雖然好用,但是我們創(chuàng)建索引是需要代價(jià)的潮峦,第一索引會(huì)導(dǎo)致占用的磁盤增大囱皿,數(shù)據(jù)越多勇婴,索引越多,磁盤占用則越大嘱腥,第二耕渴,對(duì)于每一個(gè)索引,每次進(jìn)行寫操作
(插入齿兔、更新橱脸、刪除)都將耗費(fèi)更多的時(shí)間,因?yàn)槊看胃挛臋n時(shí)分苇,還要同步更新文檔對(duì)應(yīng)的索引數(shù)據(jù)添诉,因此mongoDB規(guī)定,每個(gè)集合無論復(fù)雜度如何医寿,最多同時(shí)存在64個(gè)索引栏赴,在實(shí)踐過程中,幾乎很少會(huì)給集合創(chuàng)建超過5個(gè)索引靖秩,因此我們最好在設(shè)計(jì)集合和選擇索引組合上下一點(diǎn)心思须眷。
復(fù)合索引
如果我們需要在兩個(gè)及以上的條件上進(jìn)行查詢,甚至有時(shí)候可能會(huì)讓索引的鍵方向不同沟突,例如我們需要根據(jù)count屬性從小到大花颗,但是url則是從Z到A的順序排序,這個(gè)時(shí)候如果我們單獨(dú)給這兩個(gè)屬性設(shè)置兩個(gè)獨(dú)立的索引惠拭,查詢則不會(huì)變的很高效扩劝, 因?yàn)檫@兩個(gè)屬性都是按照指定的方向進(jìn)行排序的,如果僅僅是查詢一個(gè)屬性职辅,mongo的所以可以很容易的進(jìn)行逆序操作棒呛,但是當(dāng)多個(gè)索引列的時(shí)候就無法自動(dòng)完成快速的逆序操作了,這個(gè)時(shí)候我們就需要為這幾個(gè)屬性建立多方向的復(fù)合索引域携。例如条霜,我們需要按照url倒序,count正序的方式查詢涵亏,只要按照同樣的排序方式創(chuàng)建這兩個(gè)屬性的復(fù)合索引,如果需要按照url正序蒲凶,count也正序的這種排序查詢方式气筋,我們還需要按照這種方式設(shè)置一個(gè)復(fù)合索引,但是我們需要注意的是旋圆,如果只有一個(gè)條件宠默,索引排序的順序是相反的也可以直接逆序查詢,但是如果是多個(gè)條件一起查詢灵巧,部分屬性的索引排序方向是相反的搀矫,則會(huì)無法觸發(fā)索引抹沪,因此在多條件復(fù)合查詢的時(shí)候,索引的方向顯得尤為重要瓤球。
覆蓋索引
正常情況下我們查詢需要的數(shù)據(jù)融欧,可能只有一部分,每次查詢?nèi)绻家獙⒄麄€(gè)文檔都查詢出來卦羡,即使觸發(fā)了索引條件噪馏,但是通過索引鍵還會(huì)再去查找對(duì)應(yīng)的文檔。如果查詢中只需要查找索引中包含的字段绿饵,這個(gè)時(shí)候就際的文檔欠肾。如果你的查詢只需要查找索引中包含的字段,那就可以使用排除鍵的方式拟赊,指定返回的文檔數(shù)據(jù)中只有需要的索引鍵的數(shù)據(jù)刺桃,這樣的話,查找到索引數(shù)據(jù)后會(huì)直接返回吸祟,而不需要二次追朔文檔數(shù)據(jù)瑟慈。因此在實(shí)際中,應(yīng)該優(yōu)先使用覆蓋索引欢搜,而不是去獲取實(shí)際的文檔數(shù)據(jù)封豪。這樣可以保證工作集比較
小,但是需要注意的一點(diǎn)是炒瘟,覆蓋索引僅僅針對(duì)常規(guī)的鍵-值數(shù)據(jù)有效吹埠,如果鍵是數(shù)組數(shù)據(jù),那么無論怎么觸發(fā)疮装,都不會(huì)觸發(fā)覆蓋索引缘琅,即使我們選擇將數(shù)組數(shù)據(jù)剔除,也無法觸發(fā)覆蓋索引廓推,因此在開發(fā)設(shè)計(jì)過程中需要格外注意特殊類型的鍵設(shè)置為索引和查詢性能平衡的問題刷袍。
隱式索引
熟悉關(guān)系型數(shù)據(jù)庫的可能都知道,在mysql中樊展,如果給幾個(gè)鍵設(shè)置了聯(lián)合索引呻纹,除了自身的組合之外,還具有隱式索引的功能专缠,例如雷酪,我們給url和count設(shè)置了聯(lián)合索引,除了我們在查詢的時(shí)候按照url + count條件進(jìn)行查詢可以觸發(fā)索引以外涝婉,如果我們僅僅查詢url一個(gè)鍵哥力,會(huì)發(fā)現(xiàn)依然觸發(fā)了索引機(jī)制,但是需要注意的是,AB鍵聯(lián)合索引吩跋,只有AB順序和A作為條件才能觸發(fā)索引寞射,但是如果我們查詢順序是BA或者僅僅有B條件,是無法觸發(fā)索引的锌钮,這個(gè)就是聯(lián)合索引的隱式索引規(guī)則桥温。
同樣的,在mongoDB中也存在類似的索引機(jī)制原則轧粟,不過除了需要鍵順序以外策治,還要考慮鍵的索引方向問題,并且聯(lián)合索引的性能提升理論上是大于多個(gè)鍵的獨(dú)立索引帶來的優(yōu)勢兰吟,因此通惫,在開發(fā)設(shè)計(jì)階段,如果可以混蔼,可以盡量設(shè)計(jì)聯(lián)合索引履腋,來帶來更多隱式索引的性能優(yōu)勢,以減少單獨(dú)鍵索引帶來的額外空間開銷惭嚣。
$操作符與索引嵌套文檔
前面我們有學(xué)習(xí)mongoDB自帶的一些查詢操作符遵湖,但是需要知道的是,有部分操作符是無法利用索引機(jī)制的晚吞,會(huì)導(dǎo)致在大數(shù)據(jù)量下查詢緩慢延旧,同時(shí)也是我們不推薦且不常用的操作符。
無法利用索引的操作符
如$where
和$exists
操作符槽地,我們用來查詢和檢查一個(gè)鍵是否存在迁沫,假設(shè)文檔中有一個(gè)屬性X,我們來查詢不存在X鍵的文檔捌蚊,一般寫法如下:
{"x":{"$exists":false}}
但是在索引中集畅,不存在的字段和null的方式存儲(chǔ)的方式是一樣的,必須遍歷所有的文檔缅糟,檢查在該文檔中是否真的存在或者為null挺智,如果是稀疏索引,使用這類操作符直接會(huì)導(dǎo)致報(bào)錯(cuò)窗宦。除此之外赦颇,我們有時(shí)候也會(huì)使用$not
和$nin
操作符來取反,而取反操作符在mongoDB中效率比較低赴涵,理論上說$ne
操作符查詢沐扳,還是有可能會(huì)觸發(fā)索引的,但是因?yàn)槲覀兺枰榭此械乃饕锏臄?shù)據(jù)句占,導(dǎo)致很多時(shí)候索引根本不會(huì)被利用。例如下面的查詢語句:
db.set.find({"count":{"$ne":3}});
這個(gè)查詢?nèi)绻麚Q成普通的查詢躯嫉,即:
db.set.find({"count":{"$gt":3,"$lt":3}})
會(huì)查找所有的大于3和小于3的索引數(shù)據(jù)纱烘,如果查詢的第一個(gè)條件能過濾的數(shù)據(jù)比較多杨拐,這個(gè)時(shí)候還是會(huì)觸發(fā)索引,相對(duì)來說還是比較有效的擂啥,但是如果數(shù)據(jù)很少哄陶,那么這個(gè)時(shí)候往往不會(huì)再去觸發(fā)索引機(jī)制了,而$nin
操作符則基本上不會(huì)觸發(fā)到設(shè)置的索引了哺壶。
范圍查詢/or查詢
除了無法利用索引機(jī)制的操作符以外屋吨,我們來看一組常見的可以利用索引機(jī)制的查詢--范圍查詢和or查詢,假設(shè)我們現(xiàn)在需要查詢count大于7山宾,以及count小于15的文檔數(shù)據(jù)至扰,這個(gè)時(shí)候我們往往會(huì)利用count鍵的索引進(jìn)行快速查詢,但是我們需要知道是资锰,如果大于7篩選掉的數(shù)據(jù)比小于15篩選掉的數(shù)據(jù)集更大敢课,我們將大于7放在查詢條件前部和放在后部,查詢效率上能差很多绷杜,這也是我們推薦直秆,盡量把篩選數(shù)據(jù)更多的鍵放在條件前部的原因。但我們需要注意的是鞭盟,如果存在多個(gè)索引的情況下圾结,mongoDB并不會(huì)和mysql等數(shù)據(jù)庫一樣,只要按照順序的鍵都存在索引齿诉,可以連續(xù)觸發(fā)索引筝野,在mongoDB中正常的查詢,如果存在多個(gè)鍵都有索引的情況下鹃两,mongoDB會(huì)根據(jù)執(zhí)行計(jì)劃遗座,分析較優(yōu)的索引,選擇該索引進(jìn)行數(shù)據(jù)查詢優(yōu)化俊扳!但是我們會(huì)發(fā)現(xiàn)$or
操作符是個(gè)例外途蒋,使用$or
操作符進(jìn)行執(zhí)行計(jì)劃查看,會(huì)發(fā)現(xiàn)$or
前后的鍵都可以觸發(fā)索引馋记,但是需要注意的是$or
操作符實(shí)際上是把or前后的條件拆開号坡,分別進(jìn)行一次索引查詢進(jìn)行數(shù)據(jù)過濾,最后再將多次查詢的結(jié)果合并在一起梯醒,將重復(fù)的數(shù)據(jù)和不符合的數(shù)據(jù)進(jìn)行剔除宽堆。了解了$or
操作符的原理后,我們也能想到茸习,這樣的查詢機(jī)制肯定會(huì)比單個(gè)索引查詢來的更慢畜隶,因此在利用$or
操作符的場景下,我們可以盡可能使用例如$in
操作符來避免多次索引查詢,盡可能提升查詢的效率籽慢。
嵌套文檔/嵌套數(shù)組
mongoDB允許深入文檔內(nèi)部浸遗,對(duì)嵌套字段和嵌套數(shù)組上建立對(duì)應(yīng)的索引,例如有如下的文檔:
{
"username" : "sid",
"lock" : {
"ip" : "117.89.135.01",
"city" : "nanjing",
"state" : "NY"
}
}
我們現(xiàn)在給lock屬性上某個(gè)字段箱亿,例如city字段設(shè)置索引跛锌,以便于我們查詢的時(shí)候進(jìn)行優(yōu)化:
db.userInfo.ensureIndex({"lock.city" : 1})
不過需要注意的一點(diǎn)是,在嵌套文檔內(nèi)部建立索引和在文檔的鍵設(shè)置索引是完全不同的届惋,對(duì)嵌套文檔建立的索引髓帽,只有在查詢到嵌套文檔層才會(huì)觸發(fā)索引,例如:
db.userInfo.find({"lock":{"city":"nanjing"}})
而嵌套數(shù)組也可以建立索引脑豹,與之不同的是嵌套數(shù)組的索引是建立在每個(gè)元素的對(duì)應(yīng)字段上的郑藏,以我們的set集合為例,其中有一個(gè)ip_array字段晨缴,這個(gè)字段里面存放了每個(gè)訪問ip的信息译秦,現(xiàn)在我們給其中的ip字段設(shè)置索引:
db.set.ensureIndex({"ip_array.ip" : 1})
另外嵌套數(shù)組的索引,每個(gè)元素都會(huì)標(biāo)記一個(gè)索引字段击碗,因此實(shí)際上數(shù)組有多少條數(shù)據(jù)筑悴,就會(huì)在索引中存在多少個(gè)條目,這樣會(huì)導(dǎo)致維護(hù)數(shù)組的時(shí)候成本比一般的索引要高的多稍途,每一次的插入阁吝,修改都會(huì)重新維護(hù)索引的信息和條目順序。并且械拍,一個(gè)文檔的單個(gè)索引中最多存在一個(gè)數(shù)組字段突勇,為了避免在多鍵索引中索引條目爆炸性增長,每一對(duì)可能性的元素都會(huì)被索引坷虑,因此會(huì)導(dǎo)致假設(shè)文檔有n條數(shù)據(jù)甲馋,而每個(gè)文檔中的數(shù)組會(huì)存放m個(gè)元素,因此一個(gè)集合中索引條目的實(shí)際數(shù)量是:nm 個(gè)迄损,而不是文檔數(shù)據(jù)的n條定躏,因此在一個(gè)索引中,最多存在一個(gè)數(shù)組索引*芹敌。
索引原則和散列基數(shù)
創(chuàng)建索引的一個(gè)關(guān)鍵性原則是索引鍵的不同值的數(shù)量和比例痊远,比如,我們有一個(gè)集合氏捞,存儲(chǔ)的是用戶的信息碧聪,如果我們將gender字段設(shè)置索引列,因?yàn)樾詣e可能只有兩種液茎,如果用戶比較均勻的話逞姿,可能會(huì)導(dǎo)致散列基數(shù)接近50%辞嗡,如果性別分布不均勻,男性或者女性較多滞造,這樣就導(dǎo)致某一性別的用戶的散列基數(shù)低于40%欲间,而通常一個(gè)字段上的散列基數(shù)越高,說明不一樣的數(shù)據(jù)越多断部,而索引就能過濾更多的數(shù)據(jù)條件,效率也就會(huì)越高班缎。因此我們在設(shè)計(jì)索引鍵的時(shí)候蝴光,還需要考慮一下散列基數(shù)的問題,盡量在基數(shù)較大的字段設(shè)計(jì)索引达址。
索引類型
在創(chuàng)建索引的時(shí)候可以指定一些選項(xiàng)蔑祟,使用不同選項(xiàng)建立的索引會(huì)有不同的行為。其中常見的幾種索引類型如下:
唯一索引
唯一索引可以確保集合的每一個(gè)文檔的指定鍵都有唯一值沉唠。如果想要保證整個(gè)文檔中的url的值一定是不同的疆虚,那么就可以給url創(chuàng)建一個(gè)唯一索引,如下:
db.set.ensureIndex({"url": 1}, {unique: true});
接著我們嘗試插入兩個(gè)url一樣的數(shù)據(jù)满葛,會(huì)發(fā)現(xiàn)mongo報(bào)了如下的錯(cuò)誤:
db.set.insert({ "url" : "www.baidu.com", "count" : 5, "update_time" : "2020-08-13 12:00:00", "ip_array":[{ "ip":"192.168.1.3"}, {"ip":"192.168.1.4"}]});
//結(jié)果
> db.set.insert({ "url" : "www.baidu.com", "count" : 5, "update_time" : "2020-08-13 12:00:00", "ip_array" : [ { "ip" : "192.168.1.3" }, { "ip" : "192.168.1.4" } ] })
WriteResult({
"nInserted" : 0,
"writeError" : {
"code" : 11000,
"errmsg" : "E11000 duplicate key error collection: set.set index: url_1 dup key: { url: \"www.baidu.com\" }"
}
})
這個(gè)時(shí)候我們會(huì)發(fā)現(xiàn)url為www.baidu.com的數(shù)據(jù)只有第一次插入成功径簿,除了我們自定義設(shè)置的唯一索引鍵以外,還有一個(gè)默認(rèn)的唯一索引鍵嘀韧,我想大概猜到了--_id鍵索引篇亭!沒錯(cuò),__id是mongo中默認(rèn)給每個(gè)集合文檔設(shè)置的索引鍵锄贷,并且這個(gè)索引是無法被刪除的译蒂。
復(fù)合唯一索引
除了唯一索引以外,也可以創(chuàng)建復(fù)合的唯一索引谊却。創(chuàng)建復(fù)合唯一索引時(shí)柔昼,單個(gè)鍵的值可以相同,但所有鍵的組合值必須是唯一的炎辨。假設(shè)現(xiàn)在我們有一個(gè)url和count聯(lián)合創(chuàng)建的索引捕透,如下:
db.set.ensureIndex({"url": 1,"count":1}, {unique: true});
我們再次插入上述的數(shù)據(jù):
db.set.insert({ "url" : "www.baidu.com", "count" : 5, "update_time" : "2020-08-13 12:00:00", "ip_array":[{ "ip":"192.168.1.3"}, {"ip":"192.168.1.4"}]});
這個(gè)時(shí)候發(fā)現(xiàn)能夠插入成功,因?yàn)殡m然url存在一樣的數(shù)據(jù)蹦魔,但是count不一樣激率,這個(gè)時(shí)候復(fù)合唯一索引就不會(huì)管控插入行為,但是我們再次插入一條一樣的數(shù)據(jù)勿决,就會(huì)發(fā)現(xiàn)報(bào)了E11000 duplicate key error collection: set.set index 錯(cuò)誤乒躺。
如果在創(chuàng)建唯一索引的過程中,發(fā)現(xiàn)創(chuàng)建失敗低缩,因?yàn)樵摷系奈臋n中可能已經(jīng)存在鍵相同的重復(fù)數(shù)據(jù)了嘉冒,那么這個(gè)時(shí)候我們需要先把重復(fù)數(shù)據(jù)清理以后再次創(chuàng)建唯一索引曹货,但是我們在很多情況下,查找所有的重復(fù)數(shù)據(jù)讳推,并且清理一部分是很困難或者是很耗時(shí)的一件事顶籽,有木有什么辦法可以直接幫我們?nèi)ブ兀⑶医⑺饕匾伲窟@個(gè)時(shí)候我們就需要使用dropDups
參數(shù)了礼饱,啟用該參數(shù)會(huì)強(qiáng)制創(chuàng)建唯一索引,并且如果唯一索引鍵遇到重復(fù)數(shù)據(jù)究驴,會(huì)保留第一條數(shù)據(jù)镊绪。其他的數(shù)據(jù)都會(huì)被刪除,但是這里我們需要注意洒忧,刪除了哪些數(shù)據(jù)我們無法控制蝴韭,因此如果數(shù)據(jù)比較重要,千萬不要使用dropDups
強(qiáng)制創(chuàng)建唯一索引熙侍。
稀疏索引
前面我們說過唯一索引榄鉴,會(huì)保整個(gè)集合中指定鍵的值不會(huì)重復(fù),其中也包括不存在這個(gè)鍵的數(shù)據(jù)蛉抓,以及null的數(shù)據(jù)庆尘,這類數(shù)據(jù)也是只能存在一條,因此當(dāng)我們存入的數(shù)據(jù)芝雪,不確定唯一索引的鍵數(shù)據(jù)是否一定存在的時(shí)候减余,再次插入不存在或者null的數(shù)據(jù),會(huì)導(dǎo)致插入失敗惩系,這個(gè)時(shí)候我們可能想要唯一索引只對(duì)包含相應(yīng)鍵的文檔生效位岔。如果有一個(gè)可能存在也可能不存在的字段,但是當(dāng)它存在時(shí)堡牡,它必須是唯一的抒抬,這時(shí)就可以將unique和sparse選項(xiàng)組合在一起使用,用于創(chuàng)建稀疏索引晤柄。
當(dāng)然擦剑,熟悉mysql等關(guān)系型數(shù)據(jù)庫的知道在mysql中也存在稀疏索引的說法,不過mongo的稀疏索引和mysql完全不是一個(gè)概念芥颈,mongoDB中的稀疏索引只是不需要將每個(gè)文檔都作為索引條目惠勒。
沒有稀疏索引前,我們針對(duì)url字段進(jìn)行查詢:
db.set.find({"count":{"$ne":2}})
返回結(jié)果為爬坑,可以看到其中沒有url字段的數(shù)據(jù)也被查詢出來了:
{ "_id" : ObjectId("5f5e6a73cf72b68c1d21c471"), "url" : "www.baidu.com", "count" : 7, "update_time" : "2020-08-13 12:00:00", "ip_array" : [ { "ip" : "192.168.1.3" }, { "ip" : "192.168.1.4" } ] }
{ "_id" : ObjectId("5f91d6f1fba71470f3d5b2a8"),"update_time" : "2020-08-13 12:00:00", "ip_array" : [ { "ip" : "192.168.1.3" }, { "ip" : "192.168.1.4" } ] }
{ "_id" : ObjectId("5f91d701fba71470f3d5b2a9"), "update_time" : "2020-08-13 12:00:00", "ip_array" : [ { "ip" : "192.168.1.3" }, { "ip" : "192.168.1.4" } ] }
這個(gè)時(shí)候我們來給url字段設(shè)置稀疏索引:
db.set.ensureIndex({"url":1},{"sparse":true})
當(dāng)我們再次去查詢url不存在的數(shù)據(jù)的時(shí)候纠屋,可以看到已經(jīng)將沒有url字段的數(shù)據(jù)排除在外了:
{ "_id" : ObjectId("5f5e6a73cf72b68c1d21c471"), "url" : "www.baidu.com", "count" : 7, "update_time" : "2020-08-13 12:00:00", "ip_array" : [ { "ip" : "192.168.1.3" }, { "ip" : "192.168.1.4" } ] }
唯一稀疏索引
有時(shí)候我們需要的場景比較特殊,即盾计,需要某個(gè)字段可以不存在售担,但是要求存在的話赁遗,這個(gè)字段的值是不允許重復(fù)的,如果是使用唯一性索引族铆,那么這個(gè)字段必須存在岩四,否則null的情況只能有一條,但是如果使用稀疏索引的話哥攘,無法保證唯一性剖煌,這個(gè)時(shí)候我們就可以選擇將兩個(gè)索引合并設(shè)計(jì),即唯一性稀疏索引
db.set.ensureIndex({"count": 1}, {"unique": true,"sparse":true});
這個(gè)時(shí)候我們再去執(zhí)行查詢逝淹,會(huì)發(fā)現(xiàn)如果針對(duì)count字段查詢末捣,會(huì)自動(dòng)將沒有count字段的數(shù)據(jù)過濾,而我們插入數(shù)據(jù)的時(shí)候创橄,如果count字段存在的話,會(huì)校驗(yàn)唯一性莽红,僅允許插入一條count值不存在的數(shù)據(jù)
索引管理
在mongodb中妥畏,每個(gè)集合中同樣的索引只能建立一次,重復(fù)創(chuàng)建雖然也會(huì)提示ok安吁,但是也會(huì)提示在集合中已經(jīng)全部存在醉蚁,并且需要注意的是,所有的索引信息都保存在system.indexes集合中鬼店,這是個(gè)系統(tǒng)保留集合网棍,不可以進(jìn)行任何文檔新增和刪除操作,只能通過
ensureIndex
或者dropIndexes
/getIndexes
對(duì)其進(jìn)行操作妇智。
查看集合的索引信息
當(dāng)我們創(chuàng)建了索引以后滥玷,如果我們想要查看當(dāng)前集合中存在哪些索引,我們可以使用getIndexes
函數(shù)查看:
db.set.getIndexes();
//輸出
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "set.set"
},
{
"v" : 2,
"key" : {
"url" : 1
},
"name" : "url_1",
"ns" : "set.set",
"sparse" : true
}
]
這里有幾個(gè)比較關(guān)鍵的字段巍棱,key字段代表是哪些列一起組合設(shè)置的索引惑畴,name代表是索引的名稱,v代表當(dāng)前索引的版本航徙,對(duì)索引進(jìn)行改動(dòng)修改等都會(huì)修改v的值如贷,ns代表是當(dāng)前索引是哪個(gè)db中的哪個(gè)集合中創(chuàng)建的,而sparse字段為true到踏,則代表當(dāng)前的索引是稀疏索引杠袱。
除了查看詳情以外,我們有時(shí)候需要知道當(dāng)前索引的大小窝稿,這個(gè)時(shí)候就可以使用totalIndexSize
函數(shù)來查看索引大小:
//不指定參數(shù)或者傳遞''查詢整個(gè)集合的索引大小
db.set.totalIndexSize();
//輸出
77824
//隨便指定任何值楣富,或者{}進(jìn)行查詢,會(huì)列出來當(dāng)前集合中每個(gè)索引的大小以及總大小
db.set.totalIndexSize({})
//輸出
_id_ 36864
url_1 20480
url_sort 20480
77824
指定索引名稱
前面我們每次創(chuàng)建索引的時(shí)候都是指定了索引的策略讹躯,而我們查看了索引詳情知道每個(gè)索引都有一個(gè)唯一的名稱菩彬,事實(shí)上我們不指定索引名稱的情況下缠劝,mongoDB有默認(rèn)的索引名稱規(guī)則,即為:
key_name1_dir1_keyname2_dir2_...
其中key_name代表每個(gè)索引列的名稱骗灶,而dir則代表當(dāng)前列的索引方向惨恭,1和-1,如果我們創(chuàng)建的索引有多個(gè)索引列的情況下耙旦,這個(gè)默認(rèn)的命名會(huì)比較長脱羡,不過我們可以在創(chuàng)建索引的時(shí)候指定名稱,例如:
db.set.ensureIndex({"count":1},{"name":"count_desc"});
修改/刪除索引
隨著文檔結(jié)構(gòu)的變更免都,以及數(shù)據(jù)量的積累锉罐,我們的數(shù)據(jù)查詢方式或者條件可能會(huì)隨著產(chǎn)生變化,這個(gè)時(shí)候我們可能需要重構(gòu)新的索引绕娘,這個(gè)時(shí)候我們可以選擇的做法是將原來的索引刪除以后脓规,重新建立索引,而刪除索引有兩種方式险领,第一種是根據(jù)name進(jìn)行刪除侨舆,還有一種是將整個(gè)集合的索引除了_id以外全部刪除:
//根據(jù)name刪除
db.set.dropIndex("url_1");
//刪除當(dāng)前集合全部索引
db.set.dropIndexes();
當(dāng)我們刪除索引以后可以再次創(chuàng)建對(duì)應(yīng)的索引,但是由于數(shù)據(jù)集變大绢陌,創(chuàng)建索引的時(shí)候往往需要較長的時(shí)間挨下,這個(gè)過程會(huì)阻塞,對(duì)我們使用影響較大脐湾,這個(gè)時(shí)候我們可以在創(chuàng)建索引的時(shí)候指定background選項(xiàng)臭笆,這樣就會(huì)在后臺(tái)默默創(chuàng)建索引,不會(huì)阻塞當(dāng)前業(yè)務(wù)的執(zhí)行秤掌,如果遇到數(shù)據(jù)庫操作的時(shí)候愁铺,會(huì)先處理操作再去繼續(xù)創(chuàng)建索引,但是這種創(chuàng)建索引的方式比起直接阻塞創(chuàng)建索引會(huì)導(dǎo)致性能下降闻鉴,而且創(chuàng)建索引的時(shí)間也會(huì)變得很長,例如:
db.set.ensureIndex({"url":1},{"unique": true,"sparse":true,"background":true})