《MongoDB入門(mén)之索引》要點(diǎn):
本文介紹了MongoDB入門(mén)之索引,希望對(duì)您有用。如果有疑問(wèn),可以聯(lián)系我們。
相關(guān)主題:非關(guān)系型數(shù)據(jù)庫(kù)
索引
就像書(shū)的目錄,如果查找某內(nèi)容在沒(méi)有目錄的幫助下,只能全篇查找翻閱,這導(dǎo)致效率非常的低下;如果在借助目錄情況下,就能很快的定位具體內(nèi)容所在區(qū)域,效率會(huì)直線提高.
首先打開(kāi)命令行,輸入mongo
.默認(rèn)mongodb會(huì)連接名為test
的數(shù)據(jù)庫(kù).
? ~ mongoMongoDB shell version: 2.4.9connecting to: test> show collections>
可以使用show collections/tables
查看數(shù)據(jù)庫(kù)為空.
然后在mongodb命令行終端執(zhí)行如下代碼
> for(var i=0;i<100000;i++) {... db.users.insert({username:'user'+i})... }> show collectionssystem.indexesusers>
再查看數(shù)據(jù)庫(kù)發(fā)現(xiàn)多了system.indexes
和users
兩個(gè)表,前者即所謂的索引
,后者為新建的數(shù)據(jù)庫(kù)表.
這樣user
表中即有了10萬(wàn)
條數(shù)據(jù).
> db.users.find{ "_id" : ObjectId("5694d5da8fad9e319c5b43e4"), "username" : "user0" }{ "_id" : ObjectId("5694d5da8fad9e319c5b43e5"), "username" : "user1" }{ "_id" : ObjectId("5694d5da8fad9e319c5b43e6"), "username" : "user2" }{ "_id" : ObjectId("5694d5da8fad9e319c5b43e7"), "username" : "user3" }{ "_id" : ObjectId("5694d5da8fad9e319c5b43e8"), "username" : "user4" }{ "_id" : ObjectId("5694d5da8fad9e319c5b43e9"), "username" : "user5" }
現(xiàn)在需要查找其中任意一條數(shù)據(jù),比如
> db.users.find({username: 'user1234'}){ "_id" : ObjectId("5694d5db8fad9e319c5b48b6"), "username" : "user1234" }
發(fā)現(xiàn)這條數(shù)據(jù)成功找到,但需要了解詳細(xì)信息,需要加上explain
方法
> db.users.find({username: 'user1234'}).explain{ "cursor" : "BasicCursor", "isMultiKey" : false, "n" : 1, "nscannedObjects" : 100000, "nscanned" : 100000, "nscannedObjectsAllPlans" : 100000, "nscannedAllPlans" : 100000, "scanAndOrder" : false, "indexOnly" : false, "nYields" : 0, "nChunkSkips" : 0, "millis" : 30, "indexBounds" : { }, "server" : "root:27017"}
參數(shù)很多,目前我們只關(guān)注其中的"nscanned" : 100000
和"millis" : 30
這兩項(xiàng).
nscanned
表示mongodb在完成這個(gè)查詢過(guò)程中掃描的文檔總數(shù).可以發(fā)現(xiàn),集合中的每個(gè)文檔都被掃描了,并且總時(shí)間為30毫秒.
如果數(shù)據(jù)有1000萬(wàn)個(gè),如果每次查詢文檔都遍歷一遍.呃,時(shí)間也是相當(dāng)可觀.
對(duì)于此類查詢,索引是一個(gè)非常好的解決方案.
> db.users.ensureIndex({"username": 1})
然后再查找user1234
> db.users.ensureIndex({"username": 1})> db.users.find({username: 'user1234'}).explain{ "cursor" : "BtreeCursor username_1", "isMultiKey" : false, "n" : 1, "nscannedObjects" : 1, "nscanned" : 1, "nscannedObjectsAllPlans" : 1, "nscannedAllPlans" : 1, "scanAndOrder" : false, "indexOnly" : false, "nYields" : 0, "nChunkSkips" : 0, "millis" : 0, "indexBounds" : { "username" : [ [ "user1234", "user1234" ] ] }, "server" : "root:27017"}
的確有點(diǎn)不可思議,查詢?cè)谒查g完成,因?yàn)橥ㄟ^(guò)索引只查找了一條數(shù)據(jù),而不是100000條.
當(dāng)然使用索引是也是有代價(jià)的:對(duì)于添加的每一條索引,每次寫(xiě)操作(插入、更新、刪除)都將耗費(fèi)更多的時(shí)間.這是因?yàn)?當(dāng)數(shù)據(jù)發(fā)生變化時(shí),不僅要更新文檔,還要更新級(jí)集合上的所有索引.因此,mongodb限制每個(gè)集合最多有64個(gè)索引.通常,在一個(gè)特定的集合上,不應(yīng)該擁有兩個(gè)以上的索引.
小技巧
如果一個(gè)非常通用的查詢,或者這個(gè)查詢?cè)斐闪诵阅芷款i,那么在某字段(比如username
)建立索引是非常好的選擇.但只是給管理員用的查詢(不太在意查詢耗費(fèi)時(shí)間),就不該對(duì)這個(gè)字段建立索引.
索引的值是按一定順序排列的,所以使用索引鍵對(duì)文檔進(jìn)行排序非常快.
db.users.find.sort({'age': 1, 'username': 1})
這里先根據(jù)age排序再根據(jù)username排序,所以u(píng)sername在這里發(fā)揮的作用并不大.為了優(yōu)化這個(gè)排序,可能需要在age和username上建立索引.
db.users.ensureIndex({'age':1, 'username': 1})
這就建立了一個(gè)復(fù)合索引
(建立在多個(gè)字段上的索引),如果查詢條件包括多個(gè)鍵,這個(gè)索引就非常有用.
建立復(fù)合索引后,每個(gè)索引條目都包括一個(gè)age字段和一個(gè)username字段,并且指向文檔在磁盤(pán)上的存儲(chǔ)位置.此時(shí),age字段是嚴(yán)格升序排列的,如果age相等時(shí)再按照username升序排列.
用于查詢單個(gè)值(盡管包含這個(gè)值的文檔可能有多個(gè))
db.users.find({'age': 21}).sort({'username': -1})
因?yàn)槲覀円呀?jīng)建立好復(fù)合索引,一個(gè)age
一個(gè)username
,建立索引時(shí)使用的是升序排序(即數(shù)字1
),當(dāng)使用點(diǎn)查詢查找{age:21}
,假設(shè)仍然是10萬(wàn)條數(shù)據(jù),可能年齡是21的很多人,因此會(huì)找到不只一條數(shù)據(jù).然后sort({'username': -1})
會(huì)對(duì)這些數(shù)據(jù)進(jìn)行逆序排序,本意是這樣.但我們不要忘記建立索引時(shí)'username':1
是升序(從小到大),如果想得到逆序只要對(duì)數(shù)據(jù)從最后一個(gè)索引開(kāi)始,依次遍歷即可得到想要的結(jié)果.
排序方向并不重要,mongodb可以從任意方向?qū)λ饕M(jìn)行遍歷.
綜上,復(fù)合索引在點(diǎn)查詢這種情況非常高效,直接定位年齡,不需要對(duì)結(jié)果進(jìn)行排序,返回結(jié)果.
db.users.find({'age': {"$gte": 21, "$lte": 30}})
查找多個(gè)值相匹配的文檔.多值查詢
也可以理解為多個(gè)點(diǎn)查詢
.
如上,要查找年齡介于21到30之間.monogdb會(huì)使用索引的中的第一個(gè)鍵"age"
得到匹配的結(jié)果,而結(jié)果通常是按照索引順序排列的.
db.users.find({'age': {"$gte": 21, "$lte": 30}}).sort({'username': 1})
與上一個(gè)類似,這次需要對(duì)結(jié)果排序.
在沒(méi)有sort
時(shí),我們查詢的結(jié)果首先是根據(jù)age等于21,age等于22..這樣從小到大排序,當(dāng)age等于21有多個(gè)時(shí),在進(jìn)行username
A-Z(0-9)這樣排序.所以,sort({'username': 1})
,要將所有結(jié)果通過(guò)名字升序排列,這次不得不先在內(nèi)存中進(jìn)行排序,然后返回.效率不如上一個(gè)高.
當(dāng)然,在文檔非常少的情況,排序也花費(fèi)不了多少時(shí)間.如果結(jié)果集很大,比如超過(guò)32MB,MongoDB會(huì)拒絕對(duì)如此多的數(shù)據(jù)進(jìn)行排序工作.
還有另外一種解決方案
也可以建立另外一個(gè)索引{'username': 1, 'age': 1}
, 如果先對(duì)username
建立索引,當(dāng)再sortusername
,相當(dāng)沒(méi)有進(jìn)行排序.但是需要在整個(gè)文檔查找age等于21的帥哥美女,所以搜尋時(shí)間就長(zhǎng)了.
效率高低是分情況的,如果在沒(méi)有限制的情況下,不用進(jìn)行排序但需要搜索整個(gè)集合時(shí)間會(huì)遠(yuǎn)超過(guò)前者.但是在返回部分?jǐn)?shù)據(jù)(比如limit(1000)
),新的贏家就產(chǎn)生了.
>db.users.find({'age': {"$gte": 21, "$lte": 30}}).sort({username': 1}).limit(1000).hint({'age': 1, 'username': 1})explain['millis']
2031ms
>db.users.find({'age': {"$gte": 21, "$lte": 30}}).sort({username': 1}).limit(1000).hint({'username': 1, 'age': 1}).explain['millis']
181ms
其中可以使用hint
指定要使用的索引.
所以這種方式還是很有優(yōu)勢(shì)的.比如一般場(chǎng)景下,我們不會(huì)把所有的數(shù)據(jù)都取出來(lái),只是去查詢最近的,所以這種效率也會(huì)更高.
可以確保集合的每個(gè)文檔的指定鍵都有唯一值.
db.users.ensureIndex({'username': 1, unique: true})
比如使用mongoose框架,在定義schema時(shí),即可指定unique: true
.
如果插入2個(gè)相同都叫張三的數(shù)據(jù),第二次插入的則會(huì)失敗._id
即為唯一索引,并且不能刪除.
使用sparse
可以創(chuàng)建稀疏索引
>db.users.ensureIndex({'email': 1}, {'unique': true, 'sparse': true})
system.indexes集合中包含了每個(gè)索引的詳細(xì)信息
db.system.indexes.find
1.ensureIndex
創(chuàng)建索引
db.users.ensureIndex({'username': 1})
后臺(tái)創(chuàng)建索引,這樣數(shù)據(jù)庫(kù)再創(chuàng)建索引的同時(shí),仍然能夠處理讀寫(xiě)請(qǐng)求,可以指定background
選項(xiàng).
db.test.ensureIndex({"username":1},{"background":true})
2.getIndexes
查看索引
db.collectionName.getIndexes
db.users.getIndexes[ { "v" : 1, "key" : { "_id" : 1 }, "ns" : "test.users", "name" : "_id_" }, { "v" : 1, "key" : { "username" : 1 }, "ns" : "test.users", "name" : "username_1" }]
其中v
字段只在內(nèi)部使用,用于標(biāo)識(shí)索引版本.
3.dropIndex
刪除索引
> db.users.dropIndex("username_1"){ "nIndexesWas" : 2, "ok" : 1 }
或
> db.users.dropIndex({"username":1})
歡迎參與《MongoDB入門(mén)之索引》討論,分享您的想法,維易PHP學(xué)院為您提供專業(yè)教程。
轉(zhuǎn)載請(qǐng)注明本頁(yè)網(wǎng)址:
http://www.snjht.com/jiaocheng/10161.html