MongoDB 索引


索引是用來加快查詢的,數據庫索引與數據的索引類似,有了索引就不需要翻遍整本書,數據庫可以直接在索引中查找,

使得查詢速度很快,在索引中找到條目后,就可以直接跳轉到目標文檔的位置.

1.索引簡介

要掌握如何為查詢配置最佳索引會有些難度.

MongoDB索引幾乎和關系型數據庫的索引一樣.絕大數優化關系型數據庫索引的技巧同樣適用於MongoDB.

如:

db.refactor.insert({"username":"refactor","age":24,"isactive":true})
db.refactor.insert({"username":"refactor","age":30,"isactive":false})
db.refactor.insert({"username":"aaaaa","age":24,"isactive":false})
db.refactor.insert({"username":"aaaaa","age":34,"isactive":true})
db.refactor.insert({"username":"sssssss","age":24,"isactive":true})
db.refactor.insert({"username":"tttttt","age":24,"isactive":true})
db.refactor.insert({"username":"tttttt","age":54,"isactive":true})
db.refactor.insert({"username":"bbbbb","age":24,"isactive":false})
db.refactor.insert({"username":"rrrrr","age":24,"isactive":true})
db.refactor.insert({"username":"rrrrr","age":54,"isactive":false})

 

要按照username鍵進行查找,就可以在此鍵上建立索引,來提高查詢速度.

db.refactor.ensureIndex({"username":1})

對某個鍵創建索引會加速對該鍵的查詢,但是對於其他的查詢可能沒有幫助,即便查詢中包含了被索引的鍵.

db.refactor.find({"age":24}).sort({"age":1,"username":1})

不會用到username索引.服務器必須查找所有文檔,找到想要的日期,這個過程叫:表掃描,就是在沒有索引的書中查找

內容,要從第一頁開始,從前翻到后.通常說,應避免讓服務器做表掃描,因為集合很大時會很慢.

一定要創建查詢中用到的所有鍵索引,對於上面的查詢,應該建立age和username的索引.

db.refactor.ensureIndex({"age":1,"username":1})

傳遞給ensureIndex的文檔是一組值為1或-1的鍵,表示索引的創建方向.若索引只有一個鍵,則方向無關緊要.

若是有多個鍵,就得考慮索引的方向問題了.

如:

> db.runCommand({"dropIndexes":"refactor","index":"*"})
{
"nIndexesWas" : 2,
"msg" : "non-_id indexes dropped for collection",
"ok" : 1
}
> db.refactor.ensureIndex({"username":1,"age":1})
> db.refactor.ensureIndex({"username":1,"age":-1})
> db.system.indexes.find()
{ "v" : 1, "key" : { "_id" : 1 }, "ns" : "test.blog", "name" : "_id_" }
{ "v" : 1, "key" : { "_id" : 1 }, "ns" : "test.refactor", "name" : "_id_" }
{ "v" : 1, "key" : { "_id" : 1 }, "ns" : "test.users", "name" : "_id_" }
{ "v" : 1, "key" : { "username" : 1, "age" : 1 }, "ns" : "test.refactor", "name"
: "username_1_age_1" }
{ "v" : 1, "key" : { "username" : 1, "age" : -1 }, "ns" : "test.refactor", "name
" : "username_1_age_-1" }

如果以{"username":1,"age":1}這種方式創建索引,MongoDB會按如下方式組織:

> db.refactor.find().hint({"username":1,"age":1})
{ "_id" : ObjectId("500231f4218b8ef3edbc6f00"), "username" : "aaaaa", "age" : 24
, "isactive" : false }
{ "_id" : ObjectId("500231f4218b8ef3edbc6f01"), "username" : "aaaaa", "age" : 34
, "isactive" : true }
{ "_id" : ObjectId("500231f4218b8ef3edbc6f05"), "username" : "bbbbb", "age" : 24
, "isactive" : false }
{ "_id" : ObjectId("500231f4218b8ef3edbc6efe"), "username" : "refactor", "age" :
24, "isactive" : true }
{ "_id" : ObjectId("500231f4218b8ef3edbc6eff"), "username" : "refactor", "age" :
30, "isactive" : false }
{ "_id" : ObjectId("500231f4218b8ef3edbc6f06"), "username" : "rrrrr", "age" : 24
, "isactive" : true }
{ "_id" : ObjectId("500231f6218b8ef3edbc6f07"), "username" : "rrrrr", "age" : 54
, "isactive" : false }
{ "_id" : ObjectId("500231f4218b8ef3edbc6f02"), "username" : "sssssss", "age" :
24, "isactive" : true }
{ "_id" : ObjectId("500231f4218b8ef3edbc6f03"), "username" : "tttttt", "age" : 2
4, "isactive" : true }
{ "_id" : ObjectId("500231f4218b8ef3edbc6f04"), "username" : "tttttt", "age" : 5
4, "isactive" : true }

用戶名按照字母升序排列,同名的組按照年齡升序排列.

如果以{"username":1,"age":-1}這種方式創建索引,MongoDB會按如下方式組織:

> db.refactor.find().hint({"username":1,"age":-1})
{ "_id" : ObjectId("500231f4218b8ef3edbc6f01"), "username" : "aaaaa", "age" : 34
, "isactive" : true }
{ "_id" : ObjectId("500231f4218b8ef3edbc6f00"), "username" : "aaaaa", "age" : 24
, "isactive" : false }
{ "_id" : ObjectId("500231f4218b8ef3edbc6f05"), "username" : "bbbbb", "age" : 24
, "isactive" : false }
{ "_id" : ObjectId("500231f4218b8ef3edbc6eff"), "username" : "refactor", "age" :
30, "isactive" : false }
{ "_id" : ObjectId("500231f4218b8ef3edbc6efe"), "username" : "refactor", "age" :
24, "isactive" : true }
{ "_id" : ObjectId("500231f6218b8ef3edbc6f07"), "username" : "rrrrr", "age" : 54
, "isactive" : false }
{ "_id" : ObjectId("500231f4218b8ef3edbc6f06"), "username" : "rrrrr", "age" : 24
, "isactive" : true }
{ "_id" : ObjectId("500231f4218b8ef3edbc6f02"), "username" : "sssssss", "age" :
24, "isactive" : true }
{ "_id" : ObjectId("500231f4218b8ef3edbc6f04"), "username" : "tttttt", "age" : 5
4, "isactive" : true }
{ "_id" : ObjectId("500231f4218b8ef3edbc6f03"), "username" : "tttttt", "age" : 2
4, "isactive" : true }

用戶名按照字母升序排列,同名的組按照年齡降序排列.

 

一般來說,如果索引包含了N個鍵,則對於前幾個鍵的查詢都能利用索引,如:有個索引{"a":1,"b":1,"c":1,"d":1}

實際上是有了{"a":1},{"a":1,"b":1},{"a":1,"b":1,"c":1}索引,但是使用{"b":1},{"a":1,"c":1}等索引的查詢不會被優化.

只有使用索引前部查詢才能使用該索引.

 

MongoDB的查詢優化器會從排查詢項的順序,以便利用索引,如查詢{"username":"refactor","age":24}的時候,已經有了

{"age":1,"username":1}的索引,MongoDB會自己找到並利用它.

創建索引的缺點是每次插入,更新,刪除都會產生額外的開銷,因為數據庫不但需要執行這些操作,還要將這些操作在集合的索引中

標記.因此,盡可能少的創建索引.

有些時候,最有效的查詢是不實用查詢,一般來說,要是查詢要返回集合中一半以上的結果,用表掃描會比幾乎每條文檔都要

索引要快,所以,查詢是否存在某個鍵,或者檢查摸個布爾類型的值是真是假,就沒有必要利用索引.

 

2.擴展索引

假設有個集合存儲了用戶的狀態信息.現在要查詢用戶和日期,取出某一用戶最近的狀態.我們可能會建立

如下索引:

db.users.ensureIndex({"user":1,"date":-1})

這會使對用戶和日期的查詢非常快,但是並不是最好的方式.

因為應用會有數百萬的用戶,每人每天都有數十條狀態更新.若是每條用戶狀態的索引值咱用類似一頁紙的

磁盤控件,那么對每次"最新狀態"的查詢,數據庫將會將不同的頁載入內存.若是站點太熱門,內存放不下所有

索引,就會很慢.要是改變索引的順序{"date":-1,"user":1},則數據庫可以將最后幾天的索引保存在內存中,

可以有效的減少內存交換,這樣查詢任何用戶的最新狀態都會快很多.

 

3.索引內嵌文檔中的鍵

為內嵌文檔的鍵創建索引和為普通的鍵創建索引沒有什么區別.

db.blog.insert(
  {
    "title":"refactor's blog",
    "Content":"refactor's blog test",
    "author":
    {
      "name":"refactor",
      "email":"295240648@163.com"
    }  
  }
)

為author.name創建索引

db.blog.ensureIndex({"author.name":1})

對內嵌文檔的鍵索引和普通鍵索引沒有區別,兩者可以聯合組成復合索引.

 

3.為排序創建索引

隨着集合的增長,需要針對查詢中大量的排序做索引.如果對沒有索引的鍵調用sort,MongoDB需要將所有數據

提取到內存中來排序.因此,可以做無索引排序是有個上限的,即不可能在內存中對T級別的數據排序.按照排序來索引

以便MongoDB按照順序提取數據,這樣就能排序大規模數據,而不必擔心用光內存.

 

4.索引名稱

集合中的每個索引都有一個字符串類型的名字,來唯一標識索引,服務器通過這個名字來刪除或操作索引.默認情況下,

索引名類似 keyname1_dir1_keyname2_dir2這種形式,其中keyname代表索引的鍵,dir代表索引的方向(1或-1).

可以通過ensureIndex來指定索引的名稱.

如:

db.blog.ensureIndex({"author.name":1},{"name":"author_name_index"})

注意不能修改,只能刪除索引,再重建.

索引名有字符個數的限制,所以特別復雜的索引在創建時一定要使用自定義的名字,可以用getLastError來檢查索引

是否成功創建了或未創建成功的原因.

 

5.唯一索引

唯一索引可以確保集合的每一個文檔的指定鍵都有唯一值.如果想保證文檔的username鍵都有不同的值:

db.refactor.ensureIndex({"username":1},{"unique":true})

默認情況下,insert並不檢查文檔是否插入過了.所以為了避免插入的文檔包含與唯一鍵重復的值,可能要用安全插入

才能滿足要求,這樣,在插入這樣的文檔會看到存在重復鍵錯誤的提示.

注意,如果文檔中沒有對應的鍵,索引會認為它是以null存儲的,所以,如果對某個鍵建立了唯一索引,但插入了多個

缺少該索引鍵的文檔,這由於文檔包含null值而導致插入失敗.

 

6.消除重復

當為已有的集合創建唯一索引,可能有些值已經重復了.這樣唯一索引將創建失敗.但是,可能希望將所有包含重復值

的文檔都刪掉.dropDups選項就可以保留發現的第一個文檔,而刪除接下來的有重復值的文檔

db.refactor.ensureIndex({"username":1},{"unique":true,"dropDups":true})

如果有重要數據的話,最好還是寫個腳本預處理,而不是設置dropDups

 

7.復合唯一索引

創建復合唯一索引,單個鍵的值可以重復,只要所有鍵的值組合起來不同就行.

GridFS是MongoDB中存儲大文件的標准方式,其中就用到了復合唯一索引.

 

8.使用explain和hint

explain是一個非常有用的工具,會幫助你獲得查詢方面諸多信息.只要對游標調用該方法,可以得到查詢細節.

explain會返回一個文檔,而不是游標本身,這是與多數游標方法不同之處.

"cursor":"BtreeCursor age_1_username_1"

說明查詢使用了age_1_username_1索引.

"nscanned" : 6

6 代表數據庫查找了多少個文檔.
"n" : 6

這個代表返回文檔的數量
"millis" : 0

這個毫秒數表示數據庫執行查詢的時間.

可以通過索引名字age_1_username_1,來獲取索引的詳細信息.

db.system.indexes.find({"ns":"test.refactor","name":"age_1_username_1"})

 

如果 refactor集合有如下兩個集合:

db.refactor.ensureIndex({"username":1,"age":1})
db.refactor.ensureIndex({"age":1,"username":1})

要查詢用戶的用戶名和年齡:

db.refactor.find({"age":{"$gt":30},"username":"refactor"}).explain()

這個會用"username":1,"age":1的索引,因為是要求精確查詢用戶名和年齡范圍,數據庫自己調換了查詢項的順序.
db.refactor.find({"age":24,"username":/.*/}).explain()

這個會用"age":1,"username":1的索引

 

如果發現MongoDB用了非預期的索引,可以用hint強制使用某個索引.如:

db.refactor.find({"age":{"$gt":30},"username":"refactor"}).hint({"age":1,"username":1}).explain()

多說情況下,這種指定沒有必要,MongoDB的查詢優化器很智能,會替你選擇用哪個索引.初次做某個查詢時,

查詢優化器會同時嘗試各種查詢方案.最先完成的被確定使用,其他的則終止掉.查詢方案被記錄下來,以備日后

應對相同鍵的查詢.查詢優化器定期重試其他方案,以防止因為添加新數據后,之前的方案不是最優了.只要關心

給查詢優化器建立可以選擇的索引就可以了.

 

9.索引管理

索引的元信息存儲在每個數據庫的system.indexes集合中.這是一個 保留集合(遍歷數據庫中所有集合時要小心,因為

通常我們不想對這個集合進行操作),不能對其插入或刪除文檔.操作只能通過ensureIndex或dropIndexes進行.

system.indexes集合中包含每個索引的詳細信息.system.namespaces集合包含索引的名字.

 

10.修改索引

隨着應用程序的使用,數據庫的數據或查詢發生了改變,原來的索引不在使用.可以使用ensureIndex隨時向數據庫

添加新的索引.

db.refactor.ensureIndex({"username":1,"age":1},"background":true)

建立索引即耗時又費力,還要消耗更多資源.使用{"background":true}選項可以使這個過程在后台完成

,同時正常處理請求.要是不使用background這個選項,數據庫會阻塞建立索引期間的所有請求.

阻塞的做法會使索引建立的更快.即使在后台創建索引也會對正常操作有影響,所以最好選擇無關緊要的時間.

為已由文檔創建索引比先創建索引再插入所有文檔要稍快一些.當然,要是集合的數據從無到有,事先創建一個索引.

要是索引沒用了,可以使用dropIndexes加上索引名稱將其刪除.通常,要查一下system.indexes集合來找出索引名,

以為自動生成的名字會因驅動程序的不同而不同.

db.runCommand({"dropIndexes":"blog","index":"author.name_1"})

要刪除所有索引

db.runCommand({"dropIndexes":"blog","index":"*"})

 

11.地理空間索引

隨着移動設備的出現,找到離當前位置最近的N個場所的查詢越來越多.MongoDB為坐標平面查詢提供了專門

的索引,稱作 地理空間索引

地理空間索引也是使用ensureIndex來創建,只不過不是"1"或"-1",而是"2d"

db.map.insert({"gps":[1,100]})
db.map.insert({"gps":{"x":-30,"y":30}})
db.map.insert({"gps":{"latitude":-60,"longitude":30}})

db.map.ensureIndex({"gps":"2d"})

"gps"鍵的值必須是某種形式的一對值:一個包含兩個元素的數組或者是包含兩個鍵的內嵌文檔.內嵌文檔

的鍵名可以是隨意的,如{"gps":{"refactor":-60,"refactor1":30}

默認情況下,地理空間索引的值是-180~180(對經緯度很方便).要是想用其他值

db.map.ensureIndex({"gps":"2d"},{"min":-1000,"max":1000})

這樣就創建了一個2000光年見方的空間索引.

地理空間查詢有兩種方式:

db.map.find({"gps":{"$near":[49,-49]}})

這會按照點(49,-49)由近及遠的方式將map集合的所有文檔返回.在沒有指定limit值時,默認是100個文檔.

要是不需要那么多結果,就應該設置一個少點的值以節約資源.

db.map.find({"gps":{"$near":[49,-49]}}).limit(1)

也可以使用:

db.runCommand({geoNear:"map",near:[49,-49],num:1})

geoNear還會返回每個文檔到查詢點的距離.這個距離是以你插入的數據為單位的,如果按照經緯度的角度插入,則

距離就是經緯度.find和"$near"組合不會給出距離,但若是結果大於4M,這是唯一的選擇.

 

MongoDB不但能找到靠近一個點的文檔,還能找到指定形狀內的文檔.做法是將原來的"$near"換成"$within".

"$within"獲取形狀作為參數.這些形狀可以是 矩形,圓形等.

對於矩形:

db.map.find({"gps":{"$within":{"$box":[[10,20],[15,30]]}}})

"$box"的參數是兩個元素的數組,一個元素指定了左下角的坐標,第二個指定右上角的坐標.

對於圓形:

db.map.find({"gps":{"$within":{"$center":[[12,25],5]}}})

 

12.復合地理空間索引

應用程序要找的東西經常不只是一個地點.可以將地理空間索引與普通索引組合起來.

MongoDB的地理空間索引假設索引的內容是在一個平面上的,也就是說,對於球體的地球,並不是很精確. 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM