一、索引基礎:
MongoDB的索引幾乎與傳統的關係型資料庫一模一樣,這其中也包括一些基本的最佳化技巧。下面是建立索引的命令:
> db.test.ensureIndex({“username”:1})
可以通過下面的名稱查看索引是否已經成功建立:
> db.test.getIndexes()
刪除索引的命令是:
> db.test.dropIndex({“username”:1})
在MongoDB中,我們同樣可以建立複合索引,如:
— 數字1表示username鍵的索引按升序儲存,-1表示age鍵的索引按照降序方式儲存。
> db.test.ensureIndex({“username”:1, “age”:-1})
該索引被建立後,基於username和age的查詢將會用到該索引,或者是基於username的查詢也會用到該索引,但是只是基於age的查詢將不會用到該複合索引。因此可以說,如果想用到複合索引,必須在查詢條件中包含複合索引中的前N個索引列。然而如果查詢條件中的索引值順序和複合索引中的建立順序不一致的話,MongoDB可以智能的協助我們調整該順序,以便使複合索引可以為查詢所用。如:
> db.test.find({“age”: 30, “username”: “stephen”})
對於上面樣本中的查詢條件,MongoDB在檢索之前將會動態調整查詢條件文檔的順序,以使該查詢可以用到剛剛建立的複合索引。
我們可以為內嵌文檔建立索引,其規則和普通文檔沒有任何差別,如:
> db.test.ensureIndex({“comments.date”:1})
對於上面建立的索引,MongoDB都會根據索引的keyname和索引方向為新建立的索引自動分配一個索引名,下面的命令可以在建立索引時為其指定索引名,如:
> db.test.ensureIndex({“username”:1},{“name”:”testindex”})
隨著集合的增長,需要針對查詢中大量的排序做索引。如果沒有對索引的鍵調用sort,MongoDB需要將所有資料提取到記憶體並排序。因此在做無索引排序時,如果資料量過大以致無法在記憶體中進行排序,此時MongoDB將會報錯。
二、唯一索引:
在預設情況下建立的索引均不是唯一索引。下面的樣本將建立唯一索引,如:
> db.test.ensureIndex({“userid”:1},{“unique”:true})
如果再次插入userid重複的文檔時,MongoDB將報錯,以提示插入重複鍵,如:
> db.test.insert({“userid”:5})
> db.test.insert({“userid”:5})
E11000 duplicate key error index: test.test.$userid_1 dup key: { : 5.0 }
如果插入的文檔中不包含userid鍵,那麼該文檔中該鍵的值為null,如果多次插入類似的文檔,MongoDB將會報出同樣的錯誤,如:
> db.test.insert({“userid1″:5})
> db.test.insert({“userid1″:5})
E11000 duplicate key error index: test.test.$userid_1 dup key: { : null }
如果在建立唯一索引時已經存在了重複項,我們可以通過下面的命令協助我們在建立唯一索引時消除重複項,僅保留髮現的第一個文檔,如:
–先刪除剛剛建立的唯一索引。
> db.test.dropIndex({“userid”:1})
–插入測試資料,以保證集合中有重複鍵存在。
> db.test.remove()
> db.test.insert({“userid”:5})
> db.test.insert({“userid”:5})
–建立唯一索引,並消除重複資料。
> db.test.ensureIndex({“userid”:1},{“unique”:true,”dropDups”:true})
–查詢結果確認,重複的鍵確實在建立索引時已經被刪除。
> db.test.find()
{ “_id” : ObjectId(“4fe823c180144abd15acd52e”), “userid” : 5 }
我們同樣可以建立複合唯一索引,即保證複合索引值唯一即可。如:
> db.test.ensureIndex({“userid”:1,”age”:1},{“unique”:true})
三、鬆散索引
如果你的資料中一些行中沒有某個欄位或欄位值為null,那麼如果在這個欄位上建立普通索引,那麼無此欄位或值null的行也會參與到索引結構中,佔用相應的空間。如果我們不希望這些值為空白的行參與到我們的索引中,這時候可以採用鬆散索引,鬆散索引只會讓指定欄位不為空白的行參與到索引建立中來。建立一個鬆散索引可以用下面的命令:
db.reviews.ensureIndex({user_id: 1}, {sparse: true})
四、多值索引
MongoDB可以對一個array類型建立索引,比如像下面的結構,MongoDB可以在tags欄位上建立索引:
{ name: "Wheelbarrow",
tags: ["tools", "gardening", "soil"]
}
在產生索引時,會為tags中的三個值分別產生三個索引元素,索引中tools,gardening,soil三個值都會指向這同一行資料。相當於分裂成了三個獨立的索引項目。
三、使用explain:
explain是非常有用的工具,會協助你獲得查詢方面諸多有用的資訊。只要對遊標調用該方法,就可以得到查詢細節。explain會返回一個文檔,而不是遊標本身。如:www.111cn.net
> db.test.find().explain()
{
“cursor” : “BasicCursor”,
“nscanned” : 1,
“nscannedObjects” : 1,
“n” : 1,
“millis” : 0,
“nYields” : 0,
“nChunkSkips” : 0,
“isMultiKey” : false,
“indexOnly” : false,
“indexBounds” : {
}
}
explain會返回查詢使用的索引情況,耗時和掃描文檔數的統計資訊。
“cursor”:”BasicCursor”表示沒有使用索引。
“nscanned”:1 表示查詢了多少個文檔。
“n”:1 表示返回的文檔數量。
“millis”:0 表示整個查詢的耗時。
四、索引管理:
system.indexes集合中包含了每個索引的詳細資料,因此可以通過下面的命令查詢已經存在的索引,如:
> db.system.indexes.find()
如果在為已有資料的文檔建立索引時,可以執行下面的命令,以使MongoDB在後台建立索引,這樣的建立時就不會阻塞其他動作。但是相比而言,以阻塞方式建立索引,會使整個建立過程效率更高,但是在建立時MongoDB將無法接收其他的操作。
> db.test.ensureIndex({“username”:1},{“background”:true})
建立索引命令
實際上建立索引還有更方便的命令,那就是ensureIndex,比如我們建立一個open和close兩個欄位的聯合索引,就可以用下面的命令:
db.values.ensureIndex({open: 1, close: 1})
這個命令會觸發索引建立的兩個過程,一個是將相應的欄位排序,因為索引是按B+樹來組織的,要構建樹,將資料進行排序後能夠提高插入B+樹的效率(第二個過程的效率),在日誌中,你能看到和下面類似的輸出:
Tue Jan 4 09:58:17 [conn1] building new index on { open: 1.0, close: 1.0 } for stocks.values
1000000/4308303 23%
2000000/4308303 46%
3000000/4308303 69%
4000000/4308303 92%
Tue Jan 4 09:59:13 [conn1] external sort used : 5 files in 55 secs
第二個過程是將排序好的資料插入到索引結構中,構成可用的索引:
1200300/4308303 27%
2227900/4308303 51%
2837100/4308303 65%
3278100/4308303 76%
3783300/4308303 87%
4075500/4308303 94%
Tue Jan 4 10:00:16 [conn1] done building bottom layer, going to commit
Tue Jan 4 10:00:16 [conn1] done for 4308303 records 118.942secs
Tue Jan 4 10:00:16 [conn1] insert stocks.system.indexes 118942ms
除了日誌中的輸出外,你還可以通過在終端執行currentOp命令來擷取當前操作線程的相關資訊,如下例:
> db.currentOp()
{
"inprog" : [
{
"opid" : 58,
"active" : true,
"lockType" : "write",
"waitingForLock" : false,
"secs_running" : 55,
"op" : "insert",
"ns" : "stocks.system.indexes",
"query" : {
},
"client" : "127.0.0.1:53421",
"desc" : "conn",
"msg" : "index: (1/3) external sort 3999999/4308303 92%"
}
]
}
最後一部分就是一個索引構建過程,目前正在執行排序過程,執行到92%。
在後台建立索引 www.111cn.net
建立索引會對資料庫添加寫鎖,如果資料集比如大,會將線上讀寫資料庫的操作掛起,以等待索引建立結束。這影響了資料庫的正常服務,我們可以通過在建立索引時加background:true 的選項,讓建立工作在後台執行,這時候建立索引還是需要加寫鎖,但是這個寫鎖不會直接獨佔到索引建立完成,而是會暫停為其它讀寫操作讓路,不至於造成嚴重的效能影響。具體方法:
db.values.ensureIndex({open: 1, close: 1}, {background: true})
離線建立索引
無論如何,索引的建立都會給資料庫造成一定的壓力,從而影響線上服務。如果希望建立索引的過程完全不影響線上服務,我們可以通過將replica sets中的節點先從叢集中剝離,在這個節點上添加相應的索引,等索引添加完畢後再將其添加到replica sets中。這隻需要保證一個條件,就是建立索引的時間不能長於oplog能夠儲存日誌的時間,否則建立完後節點再上線發現再也無法追上primary了,這時會進行resync操作。
索引備份
我們知道,無論是使用mongodump還是mongoexport命令,都只是對資料進行備份,無法備份索引。我們在恢複的時候,還是需要等待漫長的索引建立過程。所以,如果你希望備份的時候帶上索引,那麼最好採用備份資料檔案的方式。
索引壓縮
索引在使用一段時間後,經曆增刪改等操作,會變得比較鬆散,從而戰用不必要的空間,我們可以通過reindex命令,重新組織索引,讓索引的空間佔用變得更小。