如何在MongoDB中构建复合索引

4

我需要关于在mongo中创建和排序索引的建议。

我有一个包含5个属性的帖子集合:

Posts

  • 状态
  • 开始日期
  • 结束日期
  • lowerCaseTitle
  • 排序顺序

几乎所有帖子都将具有相同的状态1,只有少数帖子将具有被拒绝的状态。所有我的查询都将根据状态、开始和结束日期进行过滤,并按sortOrder排序。我还将有一个查询对标题进行正则表达式搜索。

我应该在{状态:1,开始日期:1,结束日期:1,排序:1}上设置复合键吗?在复合索引中放置字段的顺序是否重要?我应该首先将状态放入复合索引中,因为它是最广泛的吗?与单个属性的单个索引相比,使用复合索引是否更好?Mongo是否仅在任何给定查询中使用单个索引?

如果我对lowerCaseTitle进行正则表达式查询,是否有任何有关索引的提示?

示例查询:

db.posts.find({status: {$gte:0}, start: {$lt: today}, end: {$gt: today}}).sort({sortOrder:1})

db.posts.find( {lowerCaseTitle: /japan/, status:{$gte:0}, start: {$lt: today}, end: {$gt: today}}).sort({sortOrder:1})
1个回答

17

这里提出了很多问题,让我按照实际顺序来回答:

  • 每个查询只能使用一个索引(除了顶级的 $or 子句等情况)。这包括任何排序。
  • 因此,您需要使用组合索引而不是针对每个字段单独建立索引。
  • 低基数字段(即在数据集中具有非常少的唯一值的字段)通常不应包含在索引中,因为它们的选择性非常有限。
  • 组合索引中字段的顺序很重要,每个字段在组合索引中的相对方向也很重要(例如“{name:1, age:-1}”)。有关组合索引和索引字段方向的大量文档都可以在mongodb.org上找到,因此我不会在这里重复所有内容。
  • 如果排序字段在索引中并且是用于选择结果集的最后一个字段之后的直接字段,则排序才会使用索引。在大多数情况下,这将是索引的最后一个字段。

因此,您根本不应该在索引中包含状态字段,因为一旦索引遍历已经基于更高基数的字段消除了绝大部分的文档,最多只剩下2-3个文档,而状态索引对于这2-3个文档来说优化效果很小(特别是您提到这些2-3个文档很可能具有相同的状态)。

现在,对于您的情况,在使用范围查询时(如您所做),它将无法使用索引进行排序。您可以通过查看 explain() 的 "scanAndOrder" 值来检查此情况。如果该值存在且为 true,则表示它将在内存中对结果集进行排序(扫描和排序),而不是直接使用索引。在您的特定情况下,这是无法避免的。

因此,您的索引应该如下:

db.posts.ensureIndex({start:1, end:1})

您的查询(为了清晰起见而修改了顺序,但查询优化器将通过相同的执行路径运行您的原始查询,但我更喜欢首先按顺序放置索引字段):

db.posts.find({start: {$lt: today}, end: {$gt: today}, status: {$gte:0}}).sort({sortOrder:1})

1
排序键不应始终位于用于查询的最后一个键之后。如果排序键早期使用,则排序在修剪之前发生。此帖子说明了这为什么有用。 - Anuj Gupta
有趣。我会进行一些测试,以查看实际的性能特征。我必须说,我对这在真实世界场景中带来性能提升持怀疑态度(即:大数据集、高范围查询的选择性)。 - Remon van Vliet
关于基数和忽略索引前缀优化,@RemonvanVliet,请问复合索引键应该按最高基数到最低基数排序还是按最低基数到最高基数排序?我查阅了 MongoDB 文档有关复合索引的内容,但并没有找到针对这个具体问题的答案;不过我猜测应该是按最高基数到最低基数排序。此外,从 2.6 版本开始,MongoDB 支持使用交集索引来创建两个索引。 - zamnuts
@zamnuts 很好的问题。我从未注意到显着差异,实际上我已经测试了性能。我认为早期更高的选择性会有助于整体性能,但我还没有尝试过足够大的数据集来可靠地进行测量。 - Remon van Vliet
1
根据文档,Mongo自2.6版本起支持在一个查询中使用多个索引:https://docs.mongodb.com/manual/core/index-intersection/ - user1587520

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接