复合索引字段顺序与 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 完全一致或完全相反。