数据库保存记录的机制是建立在文件系统上的,索引也是以文件的形式存储在磁盘上,在数据库中用到最多的索引结构就是B树。尽管索引在数据库领域是不可缺少的,但是对一个表建立过多的索引会带来一些问题,索引的建立要花费系统时间,同时索引文件也会占用磁盘空间。如果并发写入的量很大,每个插入的文档都要建立索引,可想而知,性能会较低。因此合理的建立索引是关键,搞清楚哪些字段上面需要建立索引,索引以什么样的方式建立,我们需要对每个查询过程进行分析,才能得出合理的结论。
1. 索引
在MongoDB上面,索引能够提高读操作及查询性能。没有索引,Mongodb必须扫描集合中的每一个文档,然后选择与查询条件匹配的文档,这种全表扫描的方式是非常低效的。MongoDB索引的数据结构也是B+树,它能够存储一小部分集合的数据,集体来说就是存储集合中建有索引的一个或多个字段的值,而且按照值的升序或者降序排列。对于一个查询来说,如果存在合适的索引,MongoDB能够利用这个索引减少文档的扫描数量,甚至对于某些查询能够直接从索引中返回结果,不需要再去扫描数据集合,这种查询是非常高效的。
1.1 单字段索引
MongoDB默认为所有集合都建立了一个_id字段的单字段索引,而且这个索引是唯一的,不能被删除,_id字段作为一个集合的主键,值是唯一的,对于一个集合来说,也可以在其他字段上创建单字段的唯一索引,如下面所述。
先插入一些数据:
> for(var i=1;i<10;i++){db.customers.insert({"name":"jordan"+i,"country":"American"})}
> for(var i=1;i<10;i++){db.customers.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=1;i<10;i++){db.customers.insert({"name":"ramda"+i,"country":"Malaysia"})}
下面建立单字段唯一索引
> db.customers.ensureIndex({name:1},{unique:true})
单字段唯一索引去掉{unique:true}选项就是一个普通的单字段索引。
唯一索引创建成功后,会在相应数据库的系统集合system.indexs中增加一条索引记录,如下所示:
> db.system.indexes.find()
最后一条画红线的是刚刚添加的唯一索引。索引记录中v表示索引的版本;key表示索引建立在哪个字段上;1表示索引按照升序排列;索引记录所在的命名空间,name表示唯一索引的名称。唯一索引与普通索引的区别是要求插入的所有记录在创建索引的键值上唯一。
下面执行查询,一个用索引字段作为查询选择器;一个不用索引字段作为查询选择器进行比较。
> db.customers.find({"name":"ramda9"}).explain()
以上查询语句执行返回的结果用到了刚刚创建的索引:
> db.customers.find({"country":"Malaysia"}).explain()
以上查询语句执行返回的结果没有用到刚刚创建的索引。
1.2 复合索引
MongoDB支持多个字段的复合索引,复合索引支持匹配多个字段的查询。
给上面插入的数据创建一个复合索引:
> db.customers.ensureIndex({name:1,country:1})
1.3 数组的多键索引
如果对一个值为数组类型的字段创建索引,则会默认对数组中的每一个元素创建索引。
1.4 索引管理
通过上面创建的索引可以看到,索引记录都保存在特殊的集合system.indexs中。创建索引的语法如下所示:
>db.collection.ensureIndex(keys,options)
keys是一个document文档,包含需要添加索引的字段和索引的排序方向;option是可选参数,控制索引的创建方式。
索引的删除并不是直接找到索引所在的集合system.indexs,通过在集合上执行remove命令来删除,而是通过执行集合上的命令dropIndex来删除的。例如删除上面创建的如下复合索引。
> db.customers.dropIndex("name_1_country_1")
其中参数为索引的名称。
2. 查询优化
查询优化的目的是找出慢的查询语句,分析慢的原因,然后优化此查询语句。
Mongodb对于超过100ms的查询语句,会自动地输出到日志文件里面,因此找出慢查询的第一步是查看MongoDB的日志文件,如果觉得这100ms阈值过大,可以通过mongod的服务启动选项showms来设置,它的默认值是100ms。
用上面的方法找出慢查询可能比较粗糙,第二种定位慢查询的方法是打开数据库的监视功能,它默认是关闭的,可以通过下面的命令打开。
db.setProfilingLevel(level,[slowms])
参数level是监视级别,值为0表示关闭数据库的监视功能,为1表示只记录慢查询,为2表示记录所有操作;slown为可选参数,设定慢查询的阈值。
所有监视的结果都将保存到一个特殊的集合systemn.profile中。
通过上面的两种方法可以找出慢查询的语句,然后通过建立相应的索引基本可以解决绝大部分的问题。但是有时我们需要更加精细的优化代码,这就需要分析这些慢查询的执行计划,查看查询是否用到索引,是否与我们想要的执行计划相同,用Mongodb的explian命令可以查看执行计划。