首页/数据/NoSQL 速查/覆盖查询(不回表)
MongoDB99 条

覆盖查询(不回表)

查询条件与返回字段全部来自同一个索引时,引擎无需读取文档本体;explain 显示 totalDocsExamined 为 0 即为覆盖。

NoSQL 速查数据更新于 2026-10-13

覆盖查询省掉了什么

普通索引查询要分两步:先在索引里找到文档位置,再回表读取完整文档。如果查询的过滤字段与返回字段全部来自同一个索引,引擎就不必回表,这就是覆盖查询。判断标准很直接:用 explain 看执行统计里读取的文档数是否为 0,是则说明完全没有访问集合本体。

基本信息

成立条件过滤字段与投影字段都在同一个索引里,且显式排除 _id
验证方式explain("executionStats") 中 totalDocsExamined 为 0
收益场景等值点查、小范围扫描上加分明显
失效原因多键(数组)索引无法支撑覆盖;投影里带上 _id 也会回表
适用版本各版本行为一致,字段名在 5.0 前后略有差异

典型使用场景

  • 高频点查接口:只按主键或业务键取少量字段,做到不回表
  • 统计与计数:只取索引里已有的字段做汇总
  • 列表页精简字段:列表只需要标识与状态时,用覆盖查询避免读整文档
  • 降低大文档读取成本:文档体积大时收益尤其明显
  • 容量紧张的实例:减少读取压力,间接降低缓存与 IO 需求

示例

示例
// 建立能盖住过滤与返回字段的复合索引
db.orders.createIndex({ sku: 1, qty: 1 })

// 投影必须包含索引内字段,并显式排除 _id
db.orders.find({ sku: "A-1" }, { sku: 1, qty: 1, _id: 0 }).explain("executionStats")

// 验证要点:看执行统计里的文档读取数
// 读取文档数为 0 表示没有回表,即覆盖查询成立
// db.orders.find({ sku: "A-1" }, { sku: 1, qty: 1, _id: 0 }).explain("executionStats").executionStats.totalDocsExamined

成立与失效对照

情况是否覆盖原因
过滤与返回字段都在索引内且排除 _id成立无需读取集合文档
投影里包含 _id失效_id 不在该索引内,必须回表
返回索引外的字段失效需要读取完整文档补字段
索引建在数组字段上(多键索引)失效多键索引无法提供完整字段值
索引内字段用了不同排序方向视情况方向不影响覆盖能力,但影响范围扫描效率
覆盖查询的收益来自省掉回表读取:等值点查或返回少量字段的场景最容易见效,全量扫描类需求即使覆盖也不能替代聚合与预计算。
为追求覆盖而不断加宽索引会推高写入放大与磁盘占用:索引宽度和查询收益要一起评估,别让一个查询优化拖慢整体写入。

易错点

  • 投影时忘记排除 _id:默认返回 _id 导致每次都要回表,覆盖查询其实没有成立
  • 把数组字段加进复合索引期待覆盖:多键索引无法支撑覆盖查询,加宽索引只增加写入成本
  • 只看执行计划里的索引名不看文档读取数:命中索引不等于没有回表,判断依据是读取文档数
  • 为单个查询建超宽索引:写入放大与容量占用上升,收益在低频查询上并不划算

数据来源与参考

  • MongoDB Manual: Covered Queries
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌