MongoDB索引的创建注意事项
在数据量超大的情形下,任何数据库系统在创建索引时都是一个耗时的大工程。MongoDB也不例外。因此,MongoDB索引的创建有两种选择,一个是前台方式,一个是后台方式。那这两种方式有什么差异呢,在创建索引时是否能观察到索引完成的进度呢。本文将是基于此的描述,同时也描述了索引创建相关的注意事项。
1.索引创建方式
前台方式:
缺省情况下,当为一个集合创建索引时,这个操作将阻塞其他的所有操作。即该集合上的无法正常读写,直到索引创建完毕
任意基于所有数据库申请读或写锁都将等待直到前台完成索引创建操作
后台方式:
将索引创建置于到后台,适用于那些需要长时间创建索引的情形
这样子在创建索引期间,MongoDB依旧可以正常的为提供读写操作服务
等同于关系型数据库在创建索引的时候指定online,而MongoDB则是指定background
其目的都是相同的,即在索引创建期间,尽可能的以一种占用较少的资源占用方式来实现,同时又可以提供读写服务
后台创建方式的代价:索引创建时间变长
后台创建索引的示例:
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,当你在建立索引的时候运行这些操作的会报错。