前一篇簡單介紹了索引,并給出了基本的索引使用,這一次,我們進一步說一下MongoDB中的索引,包括如何查看查詢是否走索引,如何管理索引和地理空間索引等。
【使用explain和hint】
前面講高級查詢選項時,提到過"$explain" 和 ”$hint“可以作為包裝查詢的選項關鍵字使用,其實這兩個本身就可以作為操作游標的函數調用!游標調用explain函數會返回一個文檔,用于描述當前查詢的一些細節信息。這也不同于我們前面介紹的游標函數,前面提到的游標處理函數都是返回游標,可組成方法鏈調用。我們看一下explain的具體應用
>?db.blogs.findOne();?? {?? ????????"_id"?:?ObjectId("502262ab09248743250688ea"),?? ????????"content"?:?".....",?? ????????"comment"?:?[?? ????????????????{?? ????????????????????????"author"?:?"joe",?? ????????????????????????"score"?:?3,?? ????????????????????????"comment"?:?"just?so?so!"?? ????????????????},?? ????????????????{?? ????????????????????????"author"?:?"jimmy",?? ????????????????????????"score"?:?5,?? ????????????????????????"comment"?:?"cool!?good!"?? ????????????????}?? ????????]?? }?? >?db.blogs.find({"comment.author":"joe"}).explain();?? {?? ????????"cursor"?:?"BtreeCursor?comment.author_1",?? ????????"nscanned"?:?1,?? ????????"nscannedObjects"?:?1,?? ????????"n"?:?1,?? ????????"millis"?:?70,?? ????????"nYields"?:?0,?? ????????"nChunkSkips"?:?0,?? ????????"isMultiKey"?:?true,?? ????????"indexOnly"?:?false,?? ????????"indexBounds"?:?{?? ????????????????"comment.author"?:?[?? ????????????????????????[?? ????????????????????????????????"joe",?? ????????????????????????????????"joe"?? ????????????????????????]?? ????????????????]?? ????????}?? } ?
我們在集合blogs上為內嵌文檔的鍵“comment.author”建立了索引,然后我們使用這個鍵作為查詢條件查詢文檔,在游標上調用explain返回上述文檔,我們解釋一下返回文檔幾個主要鍵的含義:
1》 “cursor”:因為這個查詢使用了索引,MongoDB中索引存儲在B樹結構中,所以這是也使用了BtreeCursor類型的游標。如果沒有使用索引,游標的類型是BasicCursor。這個鍵還會給出你所使用的索引的名稱,你通過這個名稱可以查看當前數據庫下的system.indexes集合(系統自動創建,由于存儲索引信息,這個稍微會提到)來得到索引的詳細信息。
2》 “nscanned”/“nscannedObjects”:表明當前這次查詢一共掃描了集合中多少個文檔,我們的目的是,讓這個數值和返回文檔的數量越接近越好。
3》 "n":當前查詢返回的文檔數量。
4》 “millis”:當前查詢所需時間,毫秒數。
5》 “indexBounds”:當前查詢具體使用的索引
上述我們的文檔只是定義了一個內嵌文檔的索引,我們查詢也正好使用了這個鍵,這個情況比較簡單。我們再看一個稍微復雜些的情況!集合user上有兩個索引{"name":1,“age”:1}和{“age”:1,“name”:1},我們按照這兩個鍵查詢,再看看explain的返回:
[javascript]?view plain
?copy >?db.user.ensureIndex({"name":1,"age":1});?? >?db.user.ensureIndex({"age":1,"name":1});?? >?db.user.find({"age":40,?"name":"tim"}).explain();?? {?? ????????"cursor"?:?"BtreeCursor?name_1_age_1",?? ????????"nscanned"?:?1,?? ????????"nscannedObjects"?:?1,?? ????????"n"?:?1,?? ????????"millis"?:?0,?? ????????"nYields"?:?0,?? ????????"nChunkSkips"?:?0,?? ????????"isMultiKey"?:?false,?? ????????"indexOnly"?:?false,?? ????????"indexBounds"?:?{?? ????????????????"name"?:?[?? ????????????????????????[?? ????????????????????????????????"tim",?? ????????????????????????????????"tim"?? ????????????????????????]?? ????????????????],?? ????????????????"age"?:?[?? ????????????????????????[?? ????????????????????????????????40,?? ????????????????????????????????40?? ????????????????????????]?? ????????????????]?? ????????}?? }?? >??
我們看,返回文檔的鍵沒有區別,其默認使用了索引"name_1_age_1",這是查詢優化器為我們使用的索引!我們此處可以通過hint進行更行,即強制這個查詢使用我們定義的“age_1_name_1”索引,如下:
[javascript]?view plain
?copy >?var?cursor?=?db.user.find({"age":40,?"name":"tim"}).hint({"age":1,"name":1});?? >?cursor.explain();?? {?? ????????"cursor"?:?"BtreeCursor?age_1_name_1",?? ????????"nscanned"?:?1,?? ????????"nscannedObjects"?:?1,?? ????????"n"?:?1,?? ????????"millis"?:?0,?? ????????"nYields"?:?0,?? ????????"nChunkSkips"?:?0,?? ????????"isMultiKey"?:?false,?? ????????"indexOnly"?:?false,?? ????????"indexBounds"?:?{?? ????????????????"age"?:?[?? ????????????????????????[?? ????????????????????????????????40,?? ????????????????????????????????40?? ????????????????????????]?? ????????????????],?? ????????????????"name"?:?[?? ????????????????????????[?? ????????????????????????????????"tim",?? ????????????????????????????????"tim"?? ????????????????????????]?? ????????????????]?? ????????}?? }?? >??
我們看,hint函數不同于explain函數,會返回游標,我們可以在游標上調用explain查看索引的使用情況!99%的情況,我們沒有必要通過hint去強制使用某個索引,MongoDB的查詢優化器非常智能,絕對能幫助我們使用最佳的索引去進行查詢!
【索引管理】
上面提到索引的元信息(描述信息)存儲在集合system.indexes中,這是系統提供的保留集合(創建數據庫時),我們不能對其進行插入或刪除操作!我們可以從中查看索引定義的相關信息,我們操作這個集合只能通過ensureIndex(插入索引),dropIndex(刪除索引)兩個函數!我們先看看system.indexes這個集合吧:
[javascript]?view plain
?copy >?db.system.indexes.find();?? {?"v"?:?1,?"key"?:?{?"_id"?:?1?},?"ns"?:?"mylearndb.blogs",?"name"?:?"_id_"?}?? {?"v"?:?1,?"key"?:?{?"_id"?:?1?},?"ns"?:?"mylearndb.people",?"name"?:?"_id_"?}?? {?"v"?:?1,?"key"?:?{?"_id"?:?1?},?"ns"?:?"mylearndb.users",?"name"?:?"_id_"?}??
系統會默認為集合創建"_id"唯一性索引,所以這個表會有很多這種“name”為“_id_”的索引信息。鍵“ns”是“數據庫名.集合名”,我們可以通過這個查詢一個集合上定義的所有索引:
[javascript]?view plain
?copy >?db.system.indexes.find({"ns":"mylearndb.user"});?? {?"v"?:?1,?"key"?:?{?"_id"?:?1?},?"ns"?:?"mylearndb.user",?"name"?:?"_id_"?}?? {?"v"?:?1,?"key"?:?{?"name"?:?1?},?"unique"?:?true,?"ns"?:?"mylearndb.user",?"name"?:?"name_1",?"dropDups"?:?true?}?? {?"v"?:?1,?"key"?:?{?"name"?:?1,?"age"?:?1?},?"ns"?:?"mylearndb.user",?"name"?:?"name_1_age_1"?}?? {?"v"?:?1,?"key"?:?{?"age"?:?1,?"name"?:?1?},?"ns"?:?"mylearndb.user",?"name"?:?"age_1_name_1"?}?? >??
我們也可通過鍵"name”來查詢特定索引的元信息,這里不做演示了。
【修改索引】
隨著應用數據的積累或集合結構的改變,老的索引會出現效率低下的問題,修改索引頁也是不可避免了。我們可以隨時通過ensureIndex函數為集合添加索引,這個函數前面已經多次使用,這里我們再介紹該函數第二個參數文檔的一個鍵"background",布爾類型,表明是否在數據庫服務空閑時來構建索引,因為索引的構建是一個耗時耗資源的過程,并且在構建過程中,數據庫會阻塞所有的訪問請求,對于一個大數量的集合添加索引我們應該啟用這個選型!我們還要知道的是,即時啟用了這個選項,構建仍會影響正常服務,但不會徹底阻塞數據庫服務:
[javascript]?view plain
?copy >?db.user.ensureIndex({"name":1,"registered":-1},{"background":true});?? >??
其使用方式和以前創建索引的過程沒有區別。
對于沒有用的索引,我們應盡快刪除,因為索引會影響數據庫的增刪改的效率!利用集合的dropIndex(indexName)刪除一個集合上的特定索引。我們使用這個函數的正確步驟應該是先通過查詢system.indexes確認索引的名稱,然后再刪除,這樣做是因為不是所有語言的數據庫驅動都是按照我們前面介紹的方式去生成索引名稱:
[javascript]?view plain
?copy >?db.system.indexes.find({"ns":"mylearndb.user"});?? {?"v"?:?1,?"key"?:?{?"_id"?:?1?},?"ns"?:?"mylearndb.user",?"name"?:?"_id_"?}?? {?"v"?:?1,?"key"?:?{?"name"?:?1,?"age"?:?1?},?"ns"?:?"mylearndb.user",?"name"?:?? {?"v"?:?1,?"key"?:?{?"age"?:?1,?"name"?:?1?},?"ns"?:?"mylearndb.user",?"name"?:?? >?db.user.dropIndex("name_1_age_1");?? {?"nIndexesWas"?:?3,?"ok"?:?1?}?? >?db.system.indexes.find({"ns":"mylearndb.user"});?? {?"v"?:?1,?"key"?:?{?"_id"?:?1?},?"ns"?:?"mylearndb.user",?"name"?:?"_id_"?}?? {?"v"?:?1,?"key"?:?{?"age"?:?1,?"name"?:?1?},?"ns"?:?"mylearndb.user",?"name"?:?? >??
我們需要注意,集合還有一個函數dropIndexes,不接受任何參數,這個函數要慎用啊,他會直接將集合所有的索引全部刪掉!Shell中還可以通過運行命令的方式刪除一個索引:
[javascript]?view plain
?copy >?db.system.indexes.find({"ns":"mylearndb.user"});?? {?"v"?:?1,?"key"?:?{?"_id"?:?1?},?"ns"?:?"mylearndb.user",?"name"?:?"_id_"?}?? {?"v"?:?1,?"key"?:?{?"age"?:?1,?"name"?:?1?},?"ns"?:?"mylearndb.user",?"name"?:?"age_1_name_1"?}?? >?db.runCommand({"dropIndexes":"user","index":"age_1_name_1"});?? {?"nIndexesWas"?:?2,?"ok"?:?1?}?? >?db.system.indexes.find({"ns":"mylearndb.user"});?? {?"v"?:?1,?"key"?:?{?"_id"?:?1?},?"ns"?:?"mylearndb.user",?"name"?:?"_id_"?}?? >??
從上述的例子可看到這個命令的使用情況,其中鍵"dropIndexes"指明要刪除索引所在的集合,鍵"index"指明要刪除的索引的名稱!如果這里名稱指明為一個“*”,則表明是刪除集合上所有的索引!
除此之外,還有一種刪除索引的方式是將集合刪掉,這樣所有索引(包括鍵“_id”的唯一索引)、文檔都會被刪除。上述的刪除所有索引的方式都不會刪除系統為鍵“_id”創建的唯一索引。調用集合的remove函數,即使刪除所有文檔,也不會刪除索引,當你往集合中添加數據時,該索引還會起作用。
MongoDB中,往含有數據的集合上添加索引比向空集合添加索引后插入數據要快一些,這個在進行數據庫數據初始化時可以考慮一下。
【地理空間索引】
目前網絡上LBS(location based service)越來越流行,有一個應用就是查詢你所在位置附件的某些場所。為了提升這種查詢的速度(查詢不同于上面單維度,需要搜索兩個維度),MongoDB為坐標平面查詢提供了專門的索引,即稱作地理空間索引。
由于建立地理空間索引的鍵的值必須是一對值:一個包含兩個數值的數組或包含兩個鍵的內嵌文檔(內嵌文檔的鍵的名稱無所謂),如:{“gps”:[123,134]},{“gps”:{“x”:123,“y”:134}},{“gps”:{“latitude”:123,“longitude”:134}}。這些文檔的鍵“gps”,我們都可以再上面建立地理空間索引:
[javascript]?view plain
?copy >?db.shopstreet.find();?? {?"_id"?:?ObjectId("502673678a84caa12e8070be"),?"desc"?:?"coffeehouse",?"gps"?:?[?100,?120?]?}?? {?"_id"?:?ObjectId("502673738a84caa12e8070bf"),?"desc"?:?"coffeebar",?"gps"?:?[?110,?130?]?}?? {?"_id"?:?ObjectId("502673838a84caa12e8070c0"),?"desc"?:?"coffee?king",?"gps"?:?[?110,?123?]?}?? {?"_id"?:?ObjectId("502673a08a84caa12e8070c1"),?"desc"?:?"coffee?buck",?"gps"?:?[?106,?113?]?}?? {?"_id"?:?ObjectId("502674028a84caa12e8070c2"),?"desc"?:?"nike?shop",?"gps"?:?[?109,?111?]?}?? >?db.shopstreet.ensureIndex({"gps"?:?"2d"});?? >??
建立地理空間索引同樣調用ensureIndex方法,{"gps" : "2d"},以前建立索引鍵的值為1,或-1,地理空間索引的值固定為"2d"。地理空間索引默認值的范圍為(-180~180)(對于經緯度很適合),但我們在創建索引時可以指定其值的范圍:
[javascript]?view plain
?copy >?db.shopstreet.ensureIndex({"gps"?:?"2d"},{"min":-1000,"max":1000});?? >??
上面我們創建的地理空間索引值的范圍為-1000~1000。
地理空間查詢可以通過find或使用數據庫命令。這里我們需要使用“$near”查詢操作符,如下:
[javascript]?view plain
?copy >?db.shopstreet.find({"gps":{"$near"?:?[110,130]}});?? {?"_id"?:?ObjectId("502673738a84caa12e8070bf"),?"desc"?:?"coffeebar",?"gps"?:?[?110,?130?]?}?? {?"_id"?:?ObjectId("502673838a84caa12e8070c0"),?"desc"?:?"coffee?king",?"gps"?:?[?110,?123?]?}?? {?"_id"?:?ObjectId("502673678a84caa12e8070be"),?"desc"?:?"coffeehouse",?"gps"?:?[?100,?120?]?}?? {?"_id"?:?ObjectId("502673a08a84caa12e8070c1"),?"desc"?:?"coffee?buck",?"gps"?:?[?106,?113?]?}?? {?"_id"?:?ObjectId("502674028a84caa12e8070c2"),?"desc"?:?"nike?shop",?"gps"?:?[?109,?111?]?}?? >??
這個查詢會按點(110,130)來查詢文檔,由遠及近將符合條件的文檔返回,如果沒有在游標上使用limit函數,默認會返回100條文檔。通常我們會利用limit限制前幾個最靠近的目標文檔即可!我們可以使用數據庫命令完成上述查詢:
[javascript]?view plain
?copy >?db.shopstreet.ensureIndex({"gps"?:?"2d"},{"min":-1000,"max":1000});?? >?db.shopstreet.find({"gps":{"$near"?:?[110,130]}});?? {?"_id"?:?ObjectId("502673738a84caa12e8070bf"),?"desc"?:?"coffeebar",?"gps"?:?[?110,?130?]?}?? {?"_id"?:?ObjectId("502673838a84caa12e8070c0"),?"desc"?:?"coffee?king",?"gps"?:?[?110,?123?]?}?? {?"_id"?:?ObjectId("502673678a84caa12e8070be"),?"desc"?:?"coffeehouse",?"gps"?:?[?100,?120?]?}?? {?"_id"?:?ObjectId("502673a08a84caa12e8070c1"),?"desc"?:?"coffee?buck",?"gps"?:?[?106,?113?]?}?? {?"_id"?:?ObjectId("502674028a84caa12e8070c2"),?"desc"?:?"nike?shop",?"gps"?:?[?109,?111?]?}?? >?db.runCommand({"geoNear":"shopstreet",?"near":[110,130],?"num":3});?? {?? ????????"ns"?:?"mylearndb.shopstreet",?? ????????"near"?:?"1111000111111000000111111000000111111000000111111000",?? ????????"results"?:?[?? ????????????????{?? ????????????????????????"dis"?:?0,?? ????????????????????????"obj"?:?{?? ????????????????????????????????"_id"?:?ObjectId("502673738a84caa12e8070bf"),?? ????????????????????????????????"desc"?:?"coffeebar",?? ????????????????????????????????"gps"?:?[?? ????????????????????????????????????????110,?? ????????????????????????????????????????130?? ????????????????????????????????]?? ????????????????????????}?? ????????????????},?? ????????????????{?? ????????????????????????"dis"?:?7,?? ????????????????????????"obj"?:?{?? ????????????????????????????????"_id"?:?ObjectId("502673838a84caa12e8070c0"),?? ????????????????????????????????"desc"?:?"coffee?king",?? ????????????????????????????????"gps"?:?[?? ????????????????????????????????????????110,?? ????????????????????????????????????????123?? ????????????????????????????????]?? ????????????????????????}?? ????????????????},?? ????????????????{?? ????????????????????????"dis"?:?14.142135623730951,?? ????????????????????????"obj"?:?{?? ????????????????????????????????"_id"?:?ObjectId("502673678a84caa12e8070be"),?? ????????????????????????????????"desc"?:?"coffeehouse",?? ????????????????????????????????"gps"?:?[?? ????????????????????????????????????????100,?? ????????????????????????????????????????120?? ????????????????????????????????]?? ????????????????????????}?? ????????????????}?? ????????],?? ????????"stats"?:?{?? ????????????????"time"?:?0,?? ????????????????"btreelocs"?:?0,?? ????????????????"nscanned"?:?5,?? ????????????????"objectsLoaded"?:?4,?? ????????????????"avgDistance"?:?7.04737854124365,?? ????????????????"maxDistance"?:?14.142152482638018?? ????????},?? ????????"ok"?:?1?? }?? >??
這個命令接受一個文檔,文檔中鍵"geoNear"指明查詢的集合,鍵"near"指明查詢的基準坐標值,鍵"num"指定返回的結果數量!然后執行后返回如上結果,這個命令同時還會返回每個返回文檔距查詢點的距離,這個距離的數據單位就是你數據的單位,如上述數據位經緯度,鍵“dis”后面的距離數值就是經緯度!
MongoDB不但能找到靠近一個點的文檔,還能找到指定形狀內的文檔!使用查詢操作符"$within"即可,同時通過MongoDB提供的查詢操作符指定形狀,如“$box”可以指定矩形,“$center”可以指定圓形:
[javascript]?view plain
?copy >?db.shopstreet.find();?? {?"_id"?:?ObjectId("502673678a84caa12e8070be"),?"desc"?:?"coffeehouse",?"gps"?:?[?100,?120?]?}?? {?"_id"?:?ObjectId("502673738a84caa12e8070bf"),?"desc"?:?"coffeebar",?"gps"?:?[?110,?130?]?}?? {?"_id"?:?ObjectId("502673838a84caa12e8070c0"),?"desc"?:?"coffee?king",?"gps"?:?[?110,?123?]?}?? {?"_id"?:?ObjectId("502673a08a84caa12e8070c1"),?"desc"?:?"coffee?buck",?"gps"?:?[?106,?113?]?}?? {?"_id"?:?ObjectId("502674028a84caa12e8070c2"),?"desc"?:?"nike?shop",?"gps"?:?[?109,?111?]?}?? >?db.shopstreet.find({"gps"?:?{"$within"?:?{"$box":[[109,130],[110,120]]}}});?? {?"_id"?:?ObjectId("502673838a84caa12e8070c0"),?"desc"?:?"coffee?king",?"gps"?:?[?110,?123?]?}?? {?"_id"?:?ObjectId("502673738a84caa12e8070bf"),?"desc"?:?"coffeebar",?"gps"?:?[?110,?130?]?}??
?通過{"$box":[[109,130],[110,120]]}指定一個矩形,其左下角和右上角坐標!“$within”指定查詢在這個范圍內的點。
[javascript]?view plain
?copy >?db.shopstreet.find();?? {?"_id"?:?ObjectId("502673678a84caa12e8070be"),?"desc"?:?"coffeehouse",?"gps"?:?[?100,?120?]?}?? {?"_id"?:?ObjectId("502673738a84caa12e8070bf"),?"desc"?:?"coffeebar",?"gps"?:?[?110,?130?]?}?? {?"_id"?:?ObjectId("502673838a84caa12e8070c0"),?"desc"?:?"coffee?king",?"gps"?:?[?110,?123?]?}?? {?"_id"?:?ObjectId("502673a08a84caa12e8070c1"),?"desc"?:?"coffee?buck",?"gps"?:?[?106,?113?]?}?? {?"_id"?:?ObjectId("502674028a84caa12e8070c2"),?"desc"?:?"nike?shop",?"gps"?:?[?109,?111?]?}?? >?db.shopstreet.find({"gps"?:?{"$within"?:?{"$center":[[110,130],10]}}});?? {?"_id"?:?ObjectId("502673838a84caa12e8070c0"),?"desc"?:?"coffee?king",?"gps"?:?[?110,?123?]?}?? {?"_id"?:?ObjectId("502673738a84caa12e8070bf"),?"desc"?:?"coffeebar",?"gps"?:?[?110,?130?]?}?? >??
上述是通過指定一個圓形來查詢同樣的點,{"$center":[[110,130],10]},指定了圓形的圓心坐標和半徑!
【復合地理空間索引】
通常我們查找一個位置,不會只是通過坐標去定位,還會添加其他條件,我們構建索引時也可以用上:
[javascript]?view plain
?copy >?db.shopstreet.find();?? {?"_id"?:?ObjectId("502673678a84caa12e8070be"),?"desc"?:?"coffeehouse",?"gps"?:?[?100,?120?]?}?? {?"_id"?:?ObjectId("502673738a84caa12e8070bf"),?"desc"?:?"coffeebar",?"gps"?:?[?110,?130?]?}?? {?"_id"?:?ObjectId("502673838a84caa12e8070c0"),?"desc"?:?"coffee?king",?"gps"?:?[?110,?123?]?}?? {?"_id"?:?ObjectId("502673a08a84caa12e8070c1"),?"desc"?:?"coffee?buck",?"gps"?:?[?106,?113?]?}?? {?"_id"?:?ObjectId("502674028a84caa12e8070c2"),?"desc"?:?"nike?shop",?"gps"?:?[?109,?111?]?}?? >?db.showstreet.ensureIndex({"gps":"2d",?"desc":1});?? >??
上述就創建了一個復合地理空間索引,這個索引更符合實際需要!
上述就是MongoDB中索引的使用,索引是數據庫查詢提升效率的利器,對于任何數據庫都是如此!我們應該好好掌握!
總結
以上是生活随笔為你收集整理的查看MongoDB索引的使用,管理索引的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。