如何基于“/_explain”端点判断 Mango 是否退化为全表扫描?
解读
国内一线互联网公司在面试 P6/P7 级别 CouchDB 岗位时,常把 Mango 查询优化作为“必答题”。
“/_explain”端点返回的 JSON 里藏着 CouchDB 的查询执行计划,面试官真正想考察的是:
- 你是否能快速定位 index_used 字段;
- 当索引缺失或选择器无法命中索引时,能否从 mrargs 与 covering 字段推断出“全表扫描”风险;
- 能否给出线上止血方案(建索引、改写选择器、强制索引提示)。
一句话:用“/_explain”把“慢查询”扼杀在上线前。
知识点
-
“/_explain”端点
只对 Mango 查询(POST /{db}/_find)有效,返回 JSON 包含:- index_used:实际选用的索引名,若值为 null 或 “_all_docs”,即退化为全表扫描;
- mrargs:当 start_key 与 end_key 均为 {} 或缺失,且 index_used 为 “_all_docs”,可确认扫描整个 B+Tree;
- covering:布尔值,false 表示需要回表取文档,若再叠加 index_used=null,则必为全表扫描;
- selector_rewrite:CouchDB 改写后的选择器,若出现 regex 前缀无常量,也会导致无法命中索引。
-
国内常见误区
- 以为建了索引就一定走索引:CouchDB 的 B+Tree 索引对 regex 非前缀、$not 不友好;
- 只看 “total_docs_examined” 不看 “index_used”:测试库数据量小,total_docs_examined 可能等于结果集,误导判断;
- 忽略 “partitioned” 场景:分区库下跨分区查询会强制回退 “_all_docs”,即使索引存在。
答案
步骤一:构造待验证的选择器,向“/_explain”发 POST
POST /production/_explain
Content-Type: application/json
{
"selector": {"status": "RUNNING", "created_at": {"$gte": "2024-01-01"}},
"use_index": "idx_status_created"
}
步骤二:解读返回 JSON
{
"index_used": "_all_docs",
"mrargs": {"start_key": {}, "end_key": {}},
"covering": false,
"selector_rewrite": {"status": "RUNNING", "created_at": {"$gte": "2024-01-01"}}
}
- index_used 为 “_all_docs” → 未命中任何索引;
- mrargs 的 start_key 与 end_key 均为空 → 将遍历整个主 B+Tree;
- covering=false → 每条文档都需回表;
结论:该查询已退化为全表扫描,必须立即建复合索引或改写选择器。
步骤三:线上止血
{
"index": {
"fields": ["status", "created_at"]
},
"name": "idx_status_created",
"type": "json"
}
建完后再次调用“/_explain”,确认 index_used 变为 “idx_status_created” 且 mrargs 出现具体起止键,即解除全表扫描风险。
拓展思考
-
灰度索引验证
国内大厂通常有“影子集群”,可先用 “w=0” 在影子库建索引,再对同一份数据调用“/_explain”,对比执行计划差异,避免直接在生产库建索引引发 IO 抖动。 -
复合索引顺序与选择性
当选择器为 {"department": "A", "score": {"$gte": 90}} 时,若 department 基数低而 score 基数高,应把 score 放在复合索引左侧;否则“/_explain”会显示 index_used 虽不为空,但 mrargs 的 end_key 仍很大,实际扫描行数爆炸。可通过 “bookmark” 分页采样,验证 “total_docs_examined / limit” 比值。 -
与 Redis 缓存联动
国内高并发场景下,即使“/_explain”证明已走索引,仍可能因 covering=false 导致磁盘 IO 高。可在业务层加 Redis 热缓存,Key 设计为 “couch:{selector_hash}”,TTL 30 s,把 covering=false 的查询结果缓存,降低 CouchDB 读压力。