我有一个很大的集合(约 20M 条记录),其中有一些中等文档,其中有约 20 个索引字段。所有这些索引都是单个字段。该集合也有相当多的写入和读取流量。
MongoDB 版本是 4.0.9。
我发现在高峰时段,查询优化器不断为获胜计划选择非常低效的索引。
在示例查询中:
{
name: 'Alfred Mason',
created_at: { $gt: ... },
active: true
}
所有字段均已编入索引:
{ name: 1 }
{ created_at: 1 }
{ active: 1 }
当我运行explain()
时,获胜计划将使用created_at
索引,该索引将在返回之前扫描~200k文档 4 与查询匹配。查询执行时间为~6000 毫秒。
如果我使用 $hint
强制 name
索引,它将在返回 4 之前扫描 6 个文档与查询匹配的。执行时间为~2 ms。
为什么查询优化器不断选择最慢的索引?确实令人怀疑的是,它只发生在高峰时段,此时集合有更多的写入事件,但确切的原因是什么?我能做什么呢?
在生产环境中使用$hint
安全吗?
完全删除日期字段上的索引是否合理,因为 $gt
查询似乎并不比 COLLSCAN 更快?这可能会强制查询优化器使用索引字段。但话又说回来,它也可能选择另一个低效索引( bool 字段)。
我无法使用复合索引,因为有很多用例使用所有 20 个可用索引的不同组合。
最佳答案
Mongo 似乎没有使用最佳执行计划可能有多种原因,包括:
- 使用
name
上的单字段索引估算运行时间和执行计划字段不准确。这可能是由于错误的统计数据造成的,即 Mongo 使用过时或不是最新的信息进行估计。 - 对于您的特定查询,
created_at
索引并不是最优的,一般来说,对于该字段上的大多数可能的查询,created_at
索引将是最佳的。
我的回答实际上是,考虑到您要过滤多个字段,您可能应该使用多字段索引。对于您在问题中给出的示例过滤器:
{
name: 'Alfred Mason',
created_at: { $gt: ... },
active: true
}
我建议尝试以下两个索引:
db.getCollection('your_collection').createIndex(
{ "name": 1, "created_at": 1, "active": 1 } );
和
db.getCollection('your_collection').createIndex(
{ "created_at": 1, "name": 1, "active": 1 } );
您是否想要created_at
成为索引中的第一名,或者更确切地说 name
成为第一,取决于哪个字段具有更高的基数。基数基本上意味着给定字段中所有值的唯一性。如果集合中的每个名称都是不同的,那么您可能希望名称位于第一个。另一方面,如果每个 created_at
时间戳预计是唯一的,那么将该字段放在第一位可能是有意义的。至于active
,它似乎是一个 bool 字段,因此只能采用两个值(真/假)。它应该位于索引的最后(您甚至可以完全省略它)。
关于MongoDB 查询优化器不断为查询选择效率最低的索引,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64265835/