复合索引的排序顺序
索引按升序(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 可使用非阻塞索引排序。
其他注意事项
-
不等式操作符,如 nin 是范围操作符,而不是相等操作符。
-
$regex 是一个范围操作符。
-
$in:
$in 单独使用时,它是一个执行一系列相等匹配的相等运算符。
当 $in 与 .sort() 一起使用时:
-
如果 in 类似于具有 ESR 的相等谓词。
-
如果 in 类似于具有 ESR 的范围谓词。
-
如果通常将 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)功能是指查询时利用现有的多个索引来联合处理查询条件,而不是依赖于一个复合索引或单独的索引。这种机制在某些场景下可以避免重复创建多个复合索引。
-
使用多个单字段索引联合查询
当查询条件中涉及到多个字段,而这些字段分别有单字段索引时,MongoDB 能够尝试组合这些索引的结果,从而完成查询。例如:
db.collection.createIndex({ fieldA: 1 }); db.collection.createIndex({ fieldB: 1 }); db.collection.find({ fieldA: "valueA", fieldB: "valueB" });
在这种情况下,MongoDB 会尝试使用
fieldA
和fieldB
的单字段索引,通过交叉索引来完成查询。 -
通过合并索引结果提高性能
MongoDB 查询引擎会获取fieldA
和fieldB
索引的结果,然后在内存中进行交集计算,从而筛选出满足条件的文档。这比全表扫描效率高,但不如直接使用复合索引(如{fieldA: 1, fieldB: 1}
)高效。
交叉索引的适用场景
-
条件字段不固定
如果查询条件的字段经常变动,而提前创建所有可能的复合索引代价太高,交叉索引可以降低存储和维护成本。 -
多字段查询的频率较低
如果多字段查询的使用频率较低,交叉索引提供了一种灵活的性能优化方法,而无需为低频查询创建专门的复合索引。
交叉索引的限制
-
性能不如复合索引
尽管交叉索引避免了全表扫描,但由于需要在内存中对多个索引结果进行合并,性能通常不如直接使用复合索引高效。 -
受限于查询优化器的能力
并非所有查询都能触发交叉索引优化。例如,当查询条件中字段较多或数据量较大时,MongoDB 的查询优化器可能选择放弃交叉索引,而是使用单字段索引或直接进行全表扫描。 -
查询优化器决策不透明
MongoDB 查询优化器会基于成本估算决定是否使用交叉索引,但这种决策并不总是可控或可预测。如果优化器错误估算,可能导致查询性能不佳。
如何判断是否使用了交叉索引
你可以通过查询计划(explain
)来确认 MongoDB 是否使用了交叉索引:
db.collection.find({ fieldA: "valueA", fieldB: "valueB" }).explain("executionStats");
在返回的计划中,如果看到类似 IXSCAN
(索引扫描)和 FETCH
的组合,并涉及多个索引,则说明查询使用了交叉索引。
复合索引 vs. 交叉索引
特点 | 交叉索引 | 复合索引 |
---|---|---|
灵活性 | 高,可以动态组合已有索引 | 低,需要提前规划索引 |
性能 | 中,需进行内存交集计算 | 高,直接使用复合索引 |
存储成本 | 低,只需存储单字段索引 | 高,需要额外存储复合索引 |
适用场景 | 查询字段不固定或频率低 | 查询字段固定且使用频繁 |
最佳实践
-
优先使用复合索引
如果查询频繁且条件固定,优先创建复合索引,性能更高。 -
结合业务场景灵活使用交叉索引
对于查询字段多变的情况,交叉索引提供了一种更具性价比的方式。 -
利用查询计划优化
使用explain
分析查询计划,确保 MongoDB 查询优化器选择了合理的策略。 -
监控性能瓶颈
在高负载环境中,如果交叉索引频繁使用,可能需要重新评估索引策略,避免内存计算对系统性能的影响。