首页 / MongoDB 入门教程 / 索引管理、覆盖查询与失效

MongoDB 入门教程

索引管理、覆盖查询与失效

本教程共 50 篇 · 第 46 篇 · 更新于 2026-07-30 · 约 4 分钟阅读

MongoDBMongoDB 入门教程索引管理覆盖查询索引失效选索引原则

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 选索引的原则

  1. 看区分度:字段取值越分散(如邮箱)越适合索引;性别这种只有几个值的不值得单独建。
  2. 控数量:写多读少的集合,索引宁少勿多。每个索引都拖慢写入。
  3. 贴合查询:按真实查询模式建复合索引,遵循 ESR 顺序。
  4. 用 explain 验证:每次加索引都用 explain() 看是否 IXSCAN、是否覆盖。
// 看索引实际被用的频率,找出僵尸索引
test> db.users.aggregate([{ $indexStats: {} }])
Warning

别给所有字段都建索引。我见过一个集合建了 30 个索引,写入直接卡死。索引是负债,不是资产。

46.5 小结

getIndexes/dropIndex 管理索引。覆盖查询靠「条件+投影都在索引内」省回表。失效多因跳前缀、套运算、类型错、通配正则。选索引看区分度、控数量、以 explain 为准。