mongodb的复合索引

4 阅读5分钟

复合索引的排序顺序

索引按升序(1)或降序(-1)排序顺序存储对字段的引用。

对于单字段索引,键的排序顺序并不重要,因为 MongoDB 可以沿任一方向遍历索引。

但是,对于复合索引,排序顺序可以决定索引是否支持排序操作。

例如:

db.events.find().sort( { username: 1, date: -1 } )

或者查询返回的结果首先按照用户名值降序排序,然后按照日期值升序排序,例如:

db.events.find().sort( { username: -1, date: 1 } )

下面的索引可以支持这两种排序操作:

db.events.createIndex( { "username" : 1, "date" : -1 } )
// 或者
db.events.createIndex( { "username" : -1, "date" : 1 } )

但是,上面的索引不支持通过升序用户名值和升序日期值进行排序,例如:

db.events.find().sort( { username: 1, date: 1 } )

也不支持按照用户名字段降序和按照日期字段降序的排序条件:

db.events.find().sort( { username: -1, date: -1 } )

要想支持上面的两种排序必须新建一个复合索引:

db.events.createIndex( { "username" : 1, "date" : 1 } )
// 或者
db.events.createIndex( { "username" : -1, "date" : -1 } )

总结:查询的条件与创建索引的字段排序一致或者两个字段取反后一致时,才可使用该索引。

ESR(相等、排序、范围)规则

索引键与文档字段相对应。大多数情况下,应用 ESR(相等、排序、范围)规则来排列索引键有助于创建更有效的复合索引。

相等

“相等”系指单个值的精确匹配。以下精确匹配查询扫描 cars 集合以查找 model 字段 Cordoba 精确匹配的文档。

db.cars.find( { model: "Cordoba" } )
db.cars.find( { model: { $eq: "Cordoba" } } )

精确匹配应具有选择性,即只匹配少数文档。为了减少扫描的索引键数量,请在等值匹配的测试中确保判断条件能筛选掉至少 90% 的文档。

Sort

“排序”决定结果的顺序。

排序操作在等值匹配之后,因为等值匹配会减少需要排序的文档数量。

在等值匹配之后进行排序还能让 MongoDB 进行非阻塞排序。

以下示例将查询 cars 集合。输出将按 model 进行排序:

db.cars.find( { manufacturer: "GM" } ).sort( { model: 1 } )

要提高查询性能,请对 manufacturer 和 model 字段创建索引:

db.cars.createIndex( { manufacturer: 1, model: 1 } )

manufacturer 是第一个键,因为它是相等匹配。按照与查询相同的顺序 (model) 为 1 创建索引。

范围

“范围”过滤器会扫描字段。此扫描不要求精确匹配,因此范围过滤器会松散绑定到索引键。为提高查询效率,应尽可能缩小范围边界,并使用等值匹配来限制必须扫描的文档数量。

范围筛选器类似如下内容:

db.cars.find( { price: { $gte: 15000} } )
db.cars.find( { age: { $lt: 10 } } )
db.cars.find( { priorAccidents: { $ne: null } } )

MongoDB 无法对范围过滤器的结果进行索引排序。将范围过滤器置于排序谓词之后,以便 MongoDB 可使用非阻塞索引排序。

其他注意事项

  1. 不等式操作符,如 nene 或 nin 是范围操作符,而不是相等操作符。

  2. $regex 是一个范围操作符。

  3. $in:

$in 单独使用时,它是一个执行一系列相等匹配的相等运算符。

当 $in 与 .sort() 一起使用时:

  • 如果 in的数组元素少于200,则这些元素将展开,然后按照为索引指定的排序顺序进行合并。这会提高小型数组的性能。in 的数组元素少于 200,则这些元素将展开,然后按照为索引指定的排序顺序进行合并。这会提高小型数组的性能。in 类似于具有 ESR 的相等谓词。

  • 如果 in具有200个或更多元素,则这些元素的排序方式类似于范围操作符。在这种情况下,小型数组没有实现性能提升。索引中的后续字段无法提供排序,并且in 具有 200 个或更多元素,则这些元素的排序方式类似于范围操作符。在这种情况下,小型数组没有实现性能提升。索引中的后续字段无法提供排序,并且 in 类似于具有 ESR 的范围谓词。

  • 如果通常将 ins用于小型数组,请在索引规范的前面位置包含ins 用于小型数组,请在索引规范的前面位置包含 ins。如果通常使用大型数组,请在包含范围谓词的位置包含 $ins。

注意:200 限制可能会发生变化,并且不能保证所有 MongoDB 版本都保持不变。

例子

以下查询在 cars 集合中搜索福特制造的价格超过 15,000 美元的车辆。结果按车型排序:

db.cars.find(
   {
       manufacturer: 'Ford',
       cost: { $gt: 15000 }
   } ).sort( { model: 1 } )

该查询包含了 ESR 规则中的所有元素:

  • manufacturer: 'Ford' 是基于相等的匹配操作
  • cost: { $gt: 15000 } 为基于范围的匹配操作,并且
  • model 用于排序

根据 ESR 规则,对于该示例查询,最优的索引为:

{ manufacturer: 1, model: 1, cost: 1 }

交叉索引

MongoDB 的交叉索引(Compound Index Intersecting)功能是指查询时利用现有的多个索引来联合处理查询条件,而不是依赖于一个复合索引或单独的索引。这种机制在某些场景下可以避免重复创建多个复合索引。

  1. 使用多个单字段索引联合查询
    当查询条件中涉及到多个字段,而这些字段分别有单字段索引时,MongoDB 能够尝试组合这些索引的结果,从而完成查询。

    例如:

    db.collection.createIndex({ fieldA: 1 });
    db.collection.createIndex({ fieldB: 1 });
    
    db.collection.find({ fieldA: "valueA", fieldB: "valueB" });
    

    在这种情况下,MongoDB 会尝试使用 fieldAfieldB 的单字段索引,通过交叉索引来完成查询。

  2. 通过合并索引结果提高性能
    MongoDB 查询引擎会获取 fieldAfieldB 索引的结果,然后在内存中进行交集计算,从而筛选出满足条件的文档。这比全表扫描效率高,但不如直接使用复合索引(如 {fieldA: 1, fieldB: 1})高效。

交叉索引的适用场景

  1. 条件字段不固定
    如果查询条件的字段经常变动,而提前创建所有可能的复合索引代价太高,交叉索引可以降低存储和维护成本。

  2. 多字段查询的频率较低
    如果多字段查询的使用频率较低,交叉索引提供了一种灵活的性能优化方法,而无需为低频查询创建专门的复合索引。

交叉索引的限制

  1. 性能不如复合索引
    尽管交叉索引避免了全表扫描,但由于需要在内存中对多个索引结果进行合并,性能通常不如直接使用复合索引高效。

  2. 受限于查询优化器的能力
    并非所有查询都能触发交叉索引优化。例如,当查询条件中字段较多或数据量较大时,MongoDB 的查询优化器可能选择放弃交叉索引,而是使用单字段索引或直接进行全表扫描

  3. 查询优化器决策不透明
    MongoDB 查询优化器会基于成本估算决定是否使用交叉索引,但这种决策并不总是可控或可预测。如果优化器错误估算,可能导致查询性能不佳。


如何判断是否使用了交叉索引

你可以通过查询计划(explain)来确认 MongoDB 是否使用了交叉索引:

db.collection.find({ fieldA: "valueA", fieldB: "valueB" }).explain("executionStats");

在返回的计划中,如果看到类似 IXSCAN(索引扫描)和 FETCH 的组合,并涉及多个索引,则说明查询使用了交叉索引。


复合索引 vs. 交叉索引

特点交叉索引复合索引
灵活性高,可以动态组合已有索引低,需要提前规划索引
性能中,需进行内存交集计算高,直接使用复合索引
存储成本低,只需存储单字段索引高,需要额外存储复合索引
适用场景查询字段不固定或频率低查询字段固定且使用频繁

最佳实践

  1. 优先使用复合索引
    如果查询频繁且条件固定,优先创建复合索引,性能更高。

  2. 结合业务场景灵活使用交叉索引
    对于查询字段多变的情况,交叉索引提供了一种更具性价比的方式。

  3. 利用查询计划优化
    使用 explain 分析查询计划,确保 MongoDB 查询优化器选择了合理的策略。

  4. 监控性能瓶颈
    在高负载环境中,如果交叉索引频繁使用,可能需要重新评估索引策略,避免内存计算对系统性能的影响。