文章目录
一、查询场景优化
1.1 SQL语句
- 查询语句如下:
db.getCollection('multobj').find({
$and: [
{
"time": {
"$gte": ISODate("2019-04-01T00:00:00Z"),
"$lte": ISODate("2019-05-01T23:59:59Z")
}
},
{
"sourceType": "hikCamera"
},
{
"targetType": "face"
},
{
"sourceId": {$in:["1"]}
}
]
}).sort({time:-1}).limit(100)
1.2 场景
- 简单描述一下业务场景,我们sourceId这个字段,可能会传输多个,是一个比较离散的字段,targetType和sourceType都有多种类型,
时间过滤是一定会带上的。
1.3 查询结果
在优化索引之前,在一个1000万数据的集合中,满足这个条件的记录是3.5W,这个查询耗时500余秒。非
常慢。执行计划中截图如下,使用的是sourceId+time的组合索引,我们看到这个过程扫描了24W个文档,回到上一篇文章,我们知道这样
的查询肯定是不行的,即使在数据量小的时候没有暴露问题,数据量上去了肯定很慢。
1.4 调整索引
- 调整索引。我创建了一个新的索引,索引字段是:sourceId+sourceType+targetType+time(前三个字段离散度高的放前面)。
1.5 调整后查询结果
- 然后再次查询,耗时是30毫秒,非常快。我们看下面的查询计划。结果显示这一次查询的情况非常好,扫描的文档数和索引数就是我们需要查询的
数量,数据库几乎没要做太多冗余的工作,因此非常快速。
二、如何选择索引
2.1 选择索引的字段
- 通常来说,选择索引的字段离散度较大比较好,如果一个字段离散度不大,建索引是没什么意义的。不过我感觉这个规则比较适合单键索引,在组合索引中则不一
定,比如上面我优化的组合索引,有一个字段的值只有三四种,不过或许他的离散度更大的话,索引的效果会更好。
2.2 索引顺序
- 组合索引的顺序按照{精确匹配字段,排序字段,范围查询字段},这样的索引排序会更为高效。最前面的精确匹配字段离散度越大越好,因为那样索引就能定位到小部分数据,
比如之前优化的索引中sourceId这个字段在数据库中是离散度比较大的,这样就能过滤掉大部分数据。精确匹配字段部分可能有多个,像上面的
sourceId、sourceType和targetType都是精确匹配字段,排序字段是time,最后的范围字段就是一些不等式的条件。结合这个来看
sourceId+sourceType+targetType+time这个索引其实只用到了前面2个部分,没有范围字段。这里还可以给一个例子,在另一个查询场景下我的查询包含5个字段,
这里简述为A、B、time、D、E,其中A是精确匹配的字段,B是范围字段,DE在数据库中95%的数据都包含,有这样的一个场景,查询逻辑是
A=a and (B>b1 and B <b2) and D=d and E=e and sort(time),我优化的索引是A+time+B,因为在数据库中95%的记录都都满足 D=d and E=e,因此最后2个字段没有建立
索引,优化这个索引之后,查询性能也得到了显著的提升。
2.3 索引原则
- 最佳索引必须包含查询中所有可以做过滤及需要排序的字段。
- 如果索引中的等值或者范围查询字段不能过滤出Collection中90%以上的文档,那么把它移除索引估计会更好一些,这也是我们尽量选择比较离散的字段建索引的原因。
- 按照 {等值,排序字段,范围字段}的顺序创建联合索引。
- 等值测试:在索引中加入所有需要做等值测试的字段,任意顺序(不过我觉得离散度高的在前面比较好)。
- 排序字段:(多排序字段的升/降序问题 )根据查询的顺序有序的向索引中添加字段。
- 范围过滤:以字段的基数(Collection中字段的不同值的数量)从低到高的向索引中添加范围过滤字段。
2.4 索引竞争与选择
- 我们数据库中往往创建了多个索引,那么数据库如何保证找到最佳的索引呢?这里面包含一个存储引擎对索引的竞争选择的问题,这里有一篇非常好的文章,建议大家参
考,我看了之后感觉受益匪浅。MongoDB组合索引的优化中的MongoDB的索引选择机制这一小节。
三、小结
- 本文给了一个简单的索引优化案例,给出了一般组合索引创建遵循的基本原则,具体到业务场景还需要具体分析。索引并不是越多越好,而是越少越好,索引会占据
不少内存资源,也会影响写入和修改的性能,在创建索引的时候我们需要考虑这个索引是不是值得去创建。 - 目前我们还没有涉及到查询计划中比较深入的分析,以及慢查询日志中相关的分析,这一块在后续慢慢更新。