MongoDB 索引的建立注意事項、以及建索引導致鎖庫的解決方案
MongoDB索引的建立注意事項
在資料量超大的情形下,任何資料庫系統在建立索引時都是一個耗時的大工程。MongoDB也不例外。因此,MongoDB索引的建立有兩種選擇,一個是前臺方式,一個是後臺方式。那這兩種方式有什麼差異呢,在建立索引時是否能觀察到索引完成的進度呢。本文將是基於此的描述,同時也描述了索引建立相關的注意事項。
1.索引建立方式
前臺方式:
預設情況下,當為一個集合建立索引時,這個操作將阻塞其他的所有操作。即該集合上的無法正常讀寫,直到索引建立完畢
任意基於所有資料庫申請讀或寫鎖都將等待直到前臺完成索引建立操作
後臺方式:
將索引建立置於到後臺,適用於那些需要長時間建立索引的情形
這樣子在建立索引期間,MongoDB依舊可以正常的為提供讀寫操作服務
等同於關係型資料庫在建立索引的時候指定
其目的都是相同的,即在索引建立期間,儘可能的以一種佔用較少的資源佔用方式來實現,同時又可以提供讀寫服務
後臺建立方式的代價:索引建立時間變長
後臺建立索引的示例:
db.people.createIndex( { zipcode: 1}, {background: true} )
db.people.createIndex( { city: 1}, {background: true, sparse: true } )
注意:預設情況下background選項的值為false。
2.索引建立期間注意事項
如前所述:基於後臺建立索引時,其他的資料庫操作能被完成。但是對於mongo shell會話或者你正在建立索引的這個連線將不可用,直到所有建立完畢。如果需要做一些其它的操作。則需要再建立其它的連線。
在索引建立期間,即使完成了部分索引的建立,索引依舊不可用,但是一旦建立完成即可使用。
基於後臺建立索引期間不能完成涉及該集合的相關管理操作
repairDatabase
db.collection.drop()
compact
意外中斷索引建立
如果在後臺建立索引期間,mongod例項異常終止,當mongod例項重新啟動後,未完成的索引建立將作為前臺程序來執行
如果索引建立失敗,比如由於重複的鍵等,mongod將提示錯誤並退出
在一個索引建立失敗後啟動mongod,可以使用storage.indexBuildRetry or --noIndexBuildRetry跳過索引建立來啟動
3.索引建立期間效能
後臺建立索引比前臺慢,如果索引大於實際可用記憶體,則需要更長的時間來完成索引建立;
所有涉及到該集合的相關操作在後臺期間其執行效率會下降,應在合理的維護空擋期完成索引的建立。
4.索引的命名規則
預設情況下,索引名以鍵名加上其建立順序(1或者-1)組合而成。
db.products.createIndex( { item: 1, quantity: -1 } )
比如上面的索引建立後,其索引名為item_1_quantity_-1
可以指定自定義的索引名稱
db.products.createIndex( { item: 1, quantity: -1 } , { name: "inventory_idx" } )
如上方式,我們指定了了索引名稱為inventory_idx
5.終止索引的建立
db.killOp()
6.檢視索引的建立進度
可使用 db.currentOp() 命令觀察索引建立的完成進度
//下面通過一個索引建立示例來檢視索引完成進度
//首選建立一個500w文件的集合
> for (var i=1;i<=5000000;i++){
db.inventory.insert({id:i,item:"item"+i,stock:Math.floor(i*Math.random())})
}
WriteResult({ "nInserted" : 1 })
> db.inventory.find().limit(3)
{ "_id" : ObjectId("581bfc674b0d633653f4427e"), "id" : 1, "item" : "item1", "stock" : 0 }
{ "_id" : ObjectId("581bfc674b0d633653f4427f"), "id" : 2, "item" : "item2", "stock" : 0 }
{ "_id" : ObjectId("581bfc674b0d633653f44280"), "id" : 3, "item" : "item3", "stock" : 1 }
> db.inventory.find().count()
5000000
//下面開始建立索引
> db.inventory.createIndex({item:1,unique:true})
//使用下面的命令檢視索引完成進度
> db.currentOp(
{
$or: [
{ op: "command", "query.createIndexes": { $exists: true } },
{ op: "insert", ns: /\.system\.indexes\b/ }
]
}
)
//結果如下
{
"inprog" : [
{
"desc" : "conn1", //連線描述
"threadId" : "139911670933248", //執行緒id
"connectionId" : 1,
"client" : "127.0.0.1:37524", //ip及埠
"active" : true, //活動狀態
"opid" : 5014925,
"secs_running" : 21, //已執行的時間
"microsecs_running" : NumberLong(21800738),
"op" : "command",
"ns" : "test.$cmd",
"query" : {
"createIndexes" : "inventory", //這裡描述了基於inventory正在建立索引
"indexes" : [
{
"ns" : "test.inventory",
"key" : {
"item" : 1,
"unique" : true
},
"name" : "item_1_unique_true"
}
]
},
"msg" : "Index Build Index Build: 3103284/5000000 62%", //這裡是完成的百分比
"progress" : {
"done" : 3103722,
"total" : 5000000
},
"numYields" : 0,
"locks" : { //當前持有的鎖
"Global" : "w",
"Database" : "W",
"Collection" : "w"
},
"waitingForLock" : false,
"lockStats" : { //鎖的狀態資訊
"Global" : {
"acquireCount" : {
"r" : NumberLong(1),
"w" : NumberLong(1)
}
},
"Database" : {
"acquireCount" : {
"W" : NumberLong(1)
}
},
"Collection" : {
"acquireCount" : {
"w" : NumberLong(1)
}
}
}
}
],
"ok" : 1
}
//基於後臺方式建立索引
> db.inventory.createIndex({item:1,unique:true},{background: true})
7.利用expalin進行效能查詢分析
為了測試建立索引後的效率,模擬插入十萬條資料:
>for(var i = 0; i < 100000; i++)
db.stu.insert({name:’test’+i,age:i});
建立索引前:
>db.stu.find({name:"test20000"}).explain("executionStats")
executionStats下的executionTimeMills表示整體查詢時間,單位毫秒。
建立索引後:
>db.stu.ensureIndex({“name”:1})
在執行效能分析
>db.stu.find({name:”test20000”}).explain(“executionStats”)
時間由原來的806毫秒,減少到了3毫秒,效能提升很高。
MongoDB 建立索引導致鎖庫的解決方案
背景描述
在MongoDB中,對於大資料量(百萬、千萬以及億級別)的資料建立索引,執行 db.collection.ensureIndex({key:1}) 之後,開啟另一個終端,任何操作都不能執行。
根本原因
在資料庫建立索引時,預設是“foreground” 也就是前臺建立索引,但是,當你的資料庫資料量很大時,在建立索引的時會讀取資料檔案,大量的檔案讀寫會阻止其他的操作,命令沒有顯性指定 background,所以命令會鎖庫。
解決方案
執行 db.collection.ensureIndex({key:1},{background: true}),這樣就不會鎖庫了,建立索引就會在後臺處理了。(注:“{key:1}” 中,1 表示升序 - asc,-1 表示降序 - desc )
在後臺建立索引的時候,不能對建立索引的 collection 進行一些壞滅型的操作,如:執行 repairDatabase,drop,compat,當你在建立索引的時候執行這些操作的會報錯。