首页/数据/NoSQL 速查/复合索引字段顺序与 ESR 原则
MongoDB99 条

复合索引字段顺序与 ESR 原则

复合索引按字段顺序生效:等值(Equality)字段在前、排序(Sort)字段居中、范围(Range)字段在后,能同时兼顾过滤与排序。

NoSQL 速查数据更新于 2026-10-13

复合索引字段顺序与 ESR

复合索引按字段顺序生效:字段集合相同但顺序不同,执行效率可以差一个数量级。ESR 原则给出排列依据:Equality(等值)字段在最前、Sort(排序)字段居中、Range(范围)字段放最后。原因是范围条件会截断其后字段在索引中的有序性,排在最后才不会牺牲索引对排序与前缀过滤的支持。

基本信息

E 等值等值条件字段优先,字段之间顺序相对自由,但要照顾排序与前缀需求
S 排序排序字段紧随其后,索引方向与 sort 方向一致或完全相反
R 范围范围条件字段放在最后
前缀复用{a:1,b:1,c:1} 支持仅 a、a+b、a+b+c 的查询
创建db.<coll>.createIndex({ ... })

示例设计

示例设计
// 等值 status → 排序 ts 倒序 → 范围 qty 收尾
db.orders.createIndex({ status: 1, ts: -1, qty: 1 })

// 命中:等值 + 范围 + 排序一次走完索引,无内存排序
db.orders.find({ status: "paid", qty: { $gt: 5 } }).sort({ ts: -1 })

// 前缀命中:只带 status 的查询也能用这个索引
db.orders.find({ status: "paid" })

// 跳前缀:只按 qty 查询用不上该索引,需要单独设计
db.orders.find({ qty: { $gt: 5 } }).explain("executionStats")

// 核对是否出现内存排序阶段与扫描文档数
db.orders.find({ status: "paid" }).sort({ ts: -1 }).explain("executionStats")

典型使用场景

  • 新索引设计:先把真实查询清单列出来,再按 ESR 排字段
  • 排序性能修复:排序字段放进索引可避免内存排序与超限报错
  • 分页查询:等值 + 排序 + 范围是列表页最典型的索引组合
  • 索引收敛:用一条复合索引的前缀覆盖多条查询,减少索引总数
  • 索引审计:用 $indexStats 找出长期零访问的索引,评估删除以降低写入成本

查询形态与索引可用性

查询形态能否用上 {status:1, ts:-1, qty:1}说明
仅 status 等值可用命中索引前缀
status 等值 + ts 排序可用且免内存排序排序方向与索引一致
status + qty 范围 + ts 排序可用,排序仍免范围字段放在最后是关键
仅 qty 范围不可用跳过前缀字段
qty 范围 + ts 排序不可用前缀缺失,排序要走内存
复合索引天然支持前缀:{a:1,b:1,c:1} 能被只查 a、查 a+b、查 a+b+c 的语句利用;反过来跳过 a 直接查 b 或 c 用不上它。
排序方向可以整体反转:{a:1,b:-1} 同时支持 sort({a:1,b:-1}) 与 sort({a:-1,b:1});但混合方向(索引 a:1,b:-1 配 sort a:1,b:1)不行,必须一条索引方向与 sort 完全一致或完全相反。

易错点

  • 跳过前缀字段直接查后面的字段用不上该索引,设计时先列出真实查询组合
  • 排序方向与索引不一致会导致内存排序,find 场景下超过 32MB 会直接报 Sort exceeded memory limit
  • 把范围字段放在排序字段之前,索引只能满足过滤、无法满足排序,排序退化为内存排序
  • 低选择性的等值字段放在最前会让索引扫描量偏大,等值字段之间也要按选择性排布
  • 索引越多写入越慢,靠同一复合索引前缀能覆盖的查询不要另建重复索引

数据来源与参考

  • MongoDB Manual: Compound Indexes
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌