MongoDB 索引与查询优化
MongoDB 系统讲解第一篇:02 篇已有索引入门(createIndex/explain 初体验),这一篇把索引讲全讲透——类型全家桶、复合索引的 ESR 原则、explain 输出逐字段怎么读。
索引类型全家桶
db.players.createIndex({"level": 1}) // 单字段,1 升序 -1 降序
db.players.createIndex({"server": 1, "level": -1}) // 复合索引
db.players.createIndex({"createdAt": 1}, {expireAfterSeconds: 2592000}) // TTL 索引:30 天后自动删
db.players.createIndex({"name": 1}, {unique: true}) // 唯一索引
db.players.createIndex({"email": 1}, {partialFilterExpression: {vip: true}}) // 部分索引:只索引 vip 用户
db.players.createIndex({"desc": "text"}) // 文本索引(全文搜索)
db.players.createIndex({"location": "2dsphere"}) // 地理索引
- TTL 索引是"过期自动清理"的官方方案——会话、日志类集合标配,省掉自己写定时清理任务
- 部分索引只为符合条件的子集建索引:索引更小更快,"只查活跃用户"这类场景专用
- 唯一索引注意:字段缺失时 null 也算重复值,多个文档缺字段会撞唯一约束(
sparse或部分索引可绕)
复合索引与 ESR 原则
复合索引 {"a": 1, "b": 1, "c": 1} 相当于电话簿按 a→b→c 排序。字段顺序决定索引能不能被用上、能用多少:
ESR(Equality - Sort - Range):
- E:等值条件的字段放最前
- S:排序字段放其次
- R:范围查询的字段放最后
例:查询 {"server": "X"} 按 level 排序、再筛 {"level": {"$gt": 20}}——索引应该是 {"server": 1, "level": 1}(server 是 E,level 先承担排序再承担范围,两者重合)。
💡 为什么 R 要放最后:范围字段之后的索引字段就"断档"了——按 a 查、b 范围查、c 精确查的索引
{"a":1,"b":1,"c":1},b 的范围性让 c 无法走索引前缀,只能过滤。ESR 记忆钩子:先等值、再排序、最后范围。
前缀规则:{a,b,c} 的索引可支撑 {a}、{a,b} 的查询,但撑不起 {b}——所以"每个查询建一个索引"是错的,按查询族设计复合索引。
explain:三种模式与关键字段
db.players.find({"server": "东方明珠", "level": {"$gt": 20}})
.sort({"level": -1})
.explain("executionStats") // 最常用:真的执行并给统计
逐字段读法(executionStats 层):
| 字段 | 含义 | 期望 |
|---|---|---|
nReturned |
返回条数 | — |
totalKeysExamined |
走索引扫了多少条 | 与 nReturned 接近 |
totalDocsExamined |
回表读了多少文档 | 与 nReturned 接近=索引高效 |
executionTimeMillis |
耗时 | — |
三个红旗:COLLSCAN(全集合扫描,该建索引了);totalDocsExamined >> nReturned(索引选得不准或 ESR 顺序不对);SORT stage 内存排序(排序没被索引吃掉,数据量大时可能报内存超限错误)。
索引的代价与设计清单
索引不是免费的:
- 写放大:每次 insert/update 都要同步维护所有相关索引
- 内存占用:索引要常驻内存(WiredTiger 缓存)才快,被挤出去就是灾难
- 数量红线:单集合索引别无脑堆,书里的常见建议是生产集合控制在个位数到十来个,冗余索引(被复合索引前缀覆盖的单字段索引)定期清理
我的设计清单(书里经验的汇总):
- 先看 explain 确认真的是索引问题,再建索引
- 复合索引按 ESR 排,覆盖常用查询族
- 排序字段尽量进索引,避免内存 SORT
- 分页别用大 skip(翻得越深越慢),用
{"_id": {"$gt": 上一页最后一个}}基于游标翻页 - TTL/部分索引按需上,用它们省内存
⬅️ 03-MongoDB 理解 🏠 00-数据库 ➡️ 05-MongoDB 副本集与分片
💬 评论