Mongodb索引与优化
摘要
数据库索引用到的最多的机构就是B树。尽管索引在数据库领域是必不可少的,但是对一个表建立不合适的索引也会带来问题。索引的建立需要花费时间,同时索引文件也会占用存储空间。如果并发写入的量比较大,每次写入操作都会导致索引的重建。因此合理的建立索引,需要综合考虑多方面的因素,既要保证访问的高效,又要避免因为在不合适的字段上建立索引带来的索引更新以及存储消耗。
索引
Mongodb索引也是使用B+树结构,使用索引能够极大的提高读取和查询的效率,减少检索面积。
1.1单字段索引
Mongodb默认为所有的集合创建了一个_id字段的单字段索引,而这个索引是唯一的,不可删除的,_id字段作为一个集合的主键,值是唯一的,对于一个集合来说,也可以在其他字段上创建单字段的唯一索引。
构造测试数据:
> for(var i=1;i<10;i++) db.customers.insert({name:"jordan"+i,country:"American"})
> for(var i=0;i<10;i++) db.custormers.insert({name:"gaga"+i,country:"American"})
> for(var i=1;i<10;i++) db.customers.insert({name:"ham"+i,country:"UK"})
> for(var i=1;i<10;i++) db.customers.insert({name:"brown"+i,country:"UK"})
> for(var i=0;i<10;i++) db.customers.insert({name:"ramda"+i,country:"Malaysia"})
建立一个单字段唯一索引,或者去掉{unique:true}就是一个普通的索引:
> db.customers.ensureIndex({name:1},{unique:true})
结果:
{ "v" : 1, "key" : { "name" : 1 }, unique:true, "ns" : "custormers" ,name:"name_1"}
解析:v:1表示版本
Key:{name:1}表示索引在那个字段上建立,1表示索引按照升序排列
Ns:表示索引记录所在的命名空间
Name:表示唯一的索引名称
执行一个利用索引字段为查询选择器实例
> db.customers.find({_id:"573be78595ec4db5bf23b92a"}).explain()
{
"cursor" : "IDCursor",
"n" : 0,
"nscannedObjects" : 0,
"nscanned" : 0,
"indexOnly" : false,
"millis" : 0,
"indexBounds" : {
"_id" : [
[
"573be78595ec4db5bf23b92a",
"573be78595ec4db5bf23b92a"
]
]
},
"server" : "WIN-4HMUTNF4AA6:27017"
}
解析:
Cursor:表示查询用到的索引
isMultiKey:表示查询是否用到了多建复合索引
n:反映查询选择器匹配的文档数量
nscannedObjects:表示查询的过程中总的文档扫描数目
nscanned:表示在数据库操作中扫描的文档或索引条目的总数量
nscannedObjectsAllPlans:表示反映扫描文档总数在所有查询计划中
nscannedAllPlans:表示在所有查询计划中扫描的文档或者索引条目的总数量
scanAndOrder:表示mongodb从游标取回数据时,是否进行排序
nYields:表示产生的读锁数目
millis:表示查询所需要的时间,时间为毫秒。
执行一个不使用索引的查询案例
> db.customers.find({country:"UK"}).explain()
{
"cursor" : "BasicCursor",
"isMultiKey" : false,
"n" : 18,
"nscannedObjects" : 48,
"nscanned" : 48,
"nscannedObjectsAllPlans" : 48,
"nscannedAllPlans" : 48,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 0,
"nChunkSkips" : 0,
"millis" : 0,
"server" : "WIN-4HMUTNF4AA6:27017",
"filterSet" : false
}
解析:
匹配的文档是18条,总共扫描了48次,进行了全表扫描。
1.2复合索引
构造测试数据:
> for(var i=1;i<10;i++) db.customers.insert({name:"lanbo"+i,country:"Malaysia"})
1.不使用索引查询
> db.customers.find({country:"Malaysiz"}).explain()
{
"cursor" : "BasicCursor",
"isMultiKey" : false,
"n" :18,
"nscannedObjects" : 57,
"nscanned" : 57,
"nscannedObjectsAllPlans" : 57,
"nscannedAllPlans" : 57,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 0,
"nChunkSkips" : 0,
"millis" : 0,
"server" : "WIN-4HMUTNF4AA6:27017",
"filterSet" : false
}
解析全表扫描57次,匹配文档18个。
- 添加索引查询
添加索引操作:
> db.customers.ensureIndex({country:1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
查询:
> db.customers.find({country:"Malaysia"}).explain()
{
"cursor" : "BtreeCursor country_1",
"isMultiKey" : false,
"n" : 19,
"nscannedObjects" : 19,
"nscanned" : 19,
"nscannedObjectsAllPlans" : 19,
"nscannedAllPlans" : 19,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 0,
"nChunkSkips" : 0,
"millis" : 0,
"indexBounds" : {
"country" : [
[
"Malaysia",
"Malaysia"
]
]
},
"server" : "WIN-4HMUTNF4AA6:27017",
"filterSet" : false
}
解析:扫描19次,符合查询选择器的文档19个。
- 创建复合索引
> db.customers.ensureIndex({name:1,country:1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 2,
"numIndexesAfter" : 3,
"ok" : 1
}
查询:
> db.customers.find({name:"lanbo2",country:"Malaysia"}).explain()
{
"cursor" : "BtreeCursor name_1_country_1",
"isMultiKey" : false,
"n" : 1,
"nscannedObjects" : 1,
"nscanned" : 1,
"nscannedObjectsAllPlans" : 3,
"nscannedAllPlans" : 3,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 0,
"nChunkSkips" : 0,
"millis" : 0,
"indexBounds" : {
"name" : [
[
"lanbo2",
"lanbo2"
]
],
"country" : [
[
"Malaysia",
"Malaysia"
]
]
},
"server" : "WIN-4HMUTNF4AA6:27017",
"filterSet" : false
}
解析:
扫描到的文档数据为1,匹配的文档数据也是1.
1.3数组的多键索引
如果对一个数组类型的字段创建索引,,则会默认对数组中的每一个元素创建索引。
如下集合:
{
"_id":1,
"AttributeName":"price",
"AttributeValue":["0-99,"100-299","300-399"]
"IsOptional":1
}
创建索引:
Db.DictGoodsAttribute.ensureIndex({"AttributeValue":1})
索引如下所示:
如果数组的元素值为一个嵌套的文档:
{
"_id":1,
"StatusInfo":[{"Status":9,"desc":"已经取消"}]
}
索引创建:
Db.order.ensureIndex({"StatusInfo.desc":1})
1.4索引管理
索引记录都保存在system.indexes集合中。
添加索引方法如下:
Db.collection.ensureIndex(keys,options)
Keys是一个document文档,包含需要添加索引和索引的排序方向;
Options是可选参数,控制索引的创建方式。
删除索引:
Db.collection.dropIndex(name)
Name为索引名称。
2.查询优化
查询优化的目的就是找到比较慢的查询,分析原因,优化查询速度。
那么怎么找出查询较慢的语句呢,一下给出两种方法:
方法一:
Mongodb对于超过100ms的查询语句,会自动的输出到日志文件里面,索引可以通过查询mongodb日志确定慢查询位置。
如果觉得100ms的阀值太大,可以通过mongod的服务启动项slowms来设置,它的默认值为100ms。
方法二:
打开数据库的监视功能,默认是关闭的,打开命令如下:db.setProfilingLevel(level,[slowms])
> db.setProfilingLevel(1,20)
{ "was" : 0, "slowms" : 100, "ok" : 1 }
参数level是监视级别,值为0表示关闭数据库的监视功能,为1表示只记录慢查询,为2表示记录所有操作;slowms为可选参数,设置慢查询的阀值。
一般情况下,通过以上优化能解决大多数问题,如果还有不能解决的问题,就要详细的分析查询计划,并分析索引使用情况。使用explain()可以查看查询计划。
3总结
Mongodb可以在一个集合上建立索引,而且必须为在字段_id建立一个索引,建立索引的目的与关系型数据库一样,就是为了提高对数据库的查询效率;一旦索引创建好,
Mongodb就会自动根据数据的变化维护索引,如果索引太大而不能全部保存的内存中,将会被保存的磁盘中,这样会影响查询性能,因此要时刻监控索引的大小,保证合适
的索引在内存中;监控一个查询是否用到了索引,可以在查询计划中使用explain命令。并不是所有的字段都要建立索引,我们应该根据我们的业务所涉及的查询,建立合适的索引。
如果系统有大量的写操作,由于需要维护索引的变化,会导致系统性能降低。我们在对大数据建立索引时最好在后台操作,否则会导致数据库停止响应。