MongoDB 索引怎么设计?复合索引和最左前缀怎么理解?
简化版
MongoDB 索引用来加速查询和排序,常见有单字段索引、复合索引、多键索引、文本索引、TTL 索引等。复合索引要按照高频查询条件设计,通常遵循 ESR 思路:Equality 等值字段在前,Sort 排序字段其次,Range 范围字段靠后。索引能提升读性能,但会增加写入、更新和存储成本。
详细版
常见索引:
- 单字段索引:
{ userId: 1 }; - 复合索引:
{ userId: 1, createdAt: -1 }; - 多键索引:数组字段上的索引;
- 唯一索引:保证字段唯一;
- 文本索引:文本搜索;
- TTL 索引:自动过期删除;
- 部分索引:只索引满足条件的文档。
示例:
db.orders.createIndex({ userId: 1, createdAt: -1 })
它适合:
db.orders.find({ userId: 100 }).sort({ createdAt: -1 })
不要给所有字段都建索引。索引越多,写入越慢,占用空间越多,优化器选择也更复杂。
完整版教学
一、索引解决的是扫描范围问题
没有索引时,MongoDB 可能需要扫描大量文档才能找到结果。索引像目录,可以按字段值快速定位候选文档。
例如订单集合有 1 亿条,如果按用户查订单:
db.orders.find({ userId: 100 })
没有 userId 索引,就可能扫描大量数据;有索引后,可以快速定位该用户订单。
二、复合索引要服务具体查询
复合索引:
db.orders.createIndex({ userId: 1, status: 1, createdAt: -1 })
适合:
db.orders.find({
userId: 100,
status: "PAID"
}).sort({ createdAt: -1 })
字段顺序不是随便排的。要看等值过滤、排序和范围条件的组合。
三、最左前缀怎么理解
复合索引 { a: 1, b: 1, c: 1 } 通常可以支持:
{ a: 1 }{ a: 1, b: 1 }{ a: 1, b: 1, c: 1 }
但如果查询只有 { b: 1 },就不能很好利用这个复合索引的前缀。
所以复合索引设计前,要先统计高频查询形态,而不是看字段重要程度拍脑袋。
四、ESR 是常见设计思路
MongoDB 官方文档推荐过 ESR 这类思路:等值字段、排序字段、范围字段按查询特点安排。
例如:
db.logs.createIndex({ appId: 1, level: 1, time: -1 })
适合:
db.logs.find({
appId: "pay",
level: "ERROR",
time: { $gte: start, $lt: end }
}).sort({ time: -1 })
等值字段先缩小范围,排序字段避免额外排序,范围字段通常放后面。
五、多键索引要注意数组膨胀
数组字段建索引会形成多键索引。例如:
db.articles.createIndex({ tags: 1 })
一篇文章多个标签,每个标签都会产生索引项。数组很大时,索引项数量也会膨胀。
多键索引适合标签、少量属性集合,但不适合无限增长的大数组。
六、索引的代价也很真实
索引会带来:
- 额外磁盘空间;
- 写入时维护索引;
- 更新索引字段成本;
- 内存缓存压力;
- 建索引期间的资源消耗。
所以索引优化不是越多越好,而是让关键查询有合适索引,同时定期清理低价值索引。
七、常见误区与追问
| 查询形态 | 推荐索引思路 | 说明 |
|---|---|---|
| 等值过滤 | 等值字段靠前 | 先缩小扫描范围 |
| 等值 + 排序 | 等值字段 + 排序字段 | 避免额外 SORT |
| 等值 + 范围 | 等值字段 + 范围字段 | 范围字段后续利用受限 |
| 数组字段 | 谨慎多键索引 | 数组元素会展开成多个索引项 |
记忆钩子:复合索引不是字段堆叠,而是查询路径的目录。等值先定位,排序接顺序,范围放后面收尾。
假设日志表每天 5000 万条,查询总是 appId + level + time range,索引 { appId: 1, level: 1, time: -1 } 能先按应用和级别收敛,再按时间扫描。若只建 { time: -1 },最近 1 小时所有应用日志都可能被扫描,再过滤 appId 和 level,成本会高很多。
- 误区:给所有查询字段分别建单列索引就够了。 复合查询和排序通常需要复合索引,多个单列索引未必能同时满足过滤和排序。
- 误区:复合索引字段顺序按字段重要性排。 字段顺序应按查询中的等值、排序、范围和选择性综合决定。
- 误区:索引越多查询越快。 索引会增加写入维护、磁盘空间、内存压力和优化器选择成本。
- 追问:最左前缀是什么意思? 复合索引通常要从最左字段开始连续使用,只有
{ b: 1 }很难利用{ a: 1, b: 1, c: 1 }的前缀优势。 - 追问:多键索引为什么要谨慎? 数组字段每个元素都可能生成索引项,数组越大,索引越膨胀,写入和查询成本都可能上升。
- 追问:TTL 索引适合什么? 适合会话、临时 token、过期日志等按时间自动清理的数据,不适合需要复杂业务审批的删除流程。
八、加强记忆
MongoDB 索引设计的核心是“按查询建目录”。复合索引看最左前缀,字段顺序围绕等值、排序、范围来排;索引能减少扫描和排序,但会增加写入和空间成本。