索引管理、覆盖查询与失效
本教程共 50 篇 · 第 46 篇 · 更新于 2026-07-30 · 约 4 分钟阅读
46. 索引管理、覆盖查询与失效
本节目标:会查看和删除索引,理解覆盖查询省回表的原理,避开常见失效陷阱,并掌握选索引的基本原则。
前面几章把索引类型讲全了。这一章讲「怎么管索引」和「索引为什么没生效」,都是调优时的真功夫。
46.1 查看与删除索引
getIndexes() 列出集合全部索引,含默认 _id 索引:
test> db.users.getIndexes()
删除指定索引,用名字或字段定义:
// 按名字删
test> db.users.dropIndex("idx_city_age")
// 按字段定义删
test> db.users.dropIndex({ age: 1 })
Warning一个集合最多 64 个索引。但别等到上限才收手,索引越多写入越慢,定期清理用不上的索引。
46.2 覆盖查询:不用回表
第 40 章提过「回表」:索引没覆盖全部查询字段时,要回集合取完整文档。覆盖查询(Covered Query)则让索引本身含齐所需字段,省掉回表。
做法:查询条件和返回字段都落在同一个索引里。
// 索引只含 city、age
test> db.users.createIndex({ city: 1, age: 1 })
// 只查这两个字段,且带 city 条件 -> 覆盖,不回表
test> db.users.find(
{ city: "杭州" },
{ city: 1, age: 1, _id: 0 }
).explain("queryPlanner")
explain 里若只有 IXSCAN 而没有 FETCH,且 totalDocsExamined 为 0,说明是覆盖查询。
Tip覆盖查询要显式投影、并排除
_id(因为_id不在索引里)。忘写_id: 0就会破功。
46.3 索引失效的常见场景
索引建了却没用上,多半是下面几种情况:
跳过最左前缀
复合索引 { city: 1, age: 1 },只按 age 查用不上。
// 失效:没用最左的 city
test> db.users.find({ age: { $gt: 25 } })
对字段做运算
在索引字段上套函数或运算,索引失效。
// 失效:对 age 做了运算
test> db.users.find({ $expr: { $gt: [ { $mod: [ "$age", 10 ] }, 5 ] } })
类型不匹配
索引按数字建,却用字符串查,MongoDB 不会自动转换。
// 可能失效:字符串 "28" 匹配不到数字 28 的索引项
test> db.users.find({ age: "28" })
前导通配正则
正则若不以锚点开头,无法用索引定位。
// 失效:/键盘/ 前面任意,没法走索引
test> db.products.find({ name: /键盘/ })
// 生效:/^机械/ 锚定开头,可用索引
test> db.products.find({ name: /^机械/ })
取反类操作符
$ne、$nin、$not 往往让优化器放弃索引,退化成全表扫。
Note这些都是「可能」失效,最终以
explain()的stage为准。别凭感觉下结论。
46.4 选索引的原则
- 看区分度:字段取值越分散(如邮箱)越适合索引;性别这种只有几个值的不值得单独建。
- 控数量:写多读少的集合,索引宁少勿多。每个索引都拖慢写入。
- 贴合查询:按真实查询模式建复合索引,遵循 ESR 顺序。
- 用 explain 验证:每次加索引都用
explain()看是否IXSCAN、是否覆盖。
// 看索引实际被用的频率,找出僵尸索引
test> db.users.aggregate([{ $indexStats: {} }])
Warning别给所有字段都建索引。我见过一个集合建了 30 个索引,写入直接卡死。索引是负债,不是资产。
46.5 小结
用 getIndexes/dropIndex 管理索引。覆盖查询靠「条件+投影都在索引内」省回表。失效多因跳前缀、套运算、类型错、通配正则。选索引看区分度、控数量、以 explain 为准。