当“docs_examined”远大于“docs_returned”时,如何优化?
解读
在国内 CouchDB 生产环境中,运维同学常在 Fauxton 的“Active Tasks”或日志里发现 docs_examined 是 docs_returned 的几十倍甚至上千倍。面试官抛出该问题,核心想验证两点:
- 你是否真正理解 Mango/视图的工作机制;
- 能否用最小代价把“全表扫描”变成“精准命中”,并兼顾国内常见的云主机 IO 瓶颈与移动断网同步场景。
知识点
- B+Tree 索引结构:CouchDB 的视图索引是 B+Tree,只能按“前缀有序”快速定位;一旦查询字段顺序与索引不一致就会退化成顺序扫描。
- 覆盖索引(Covering Index):如果索引条目中已包含 select-list 里的全部字段,CouchDB 可直接从索引返回结果,不再回读文档,显著减少 docs_examined。
- Partial Index:国内很多业务把“软删除”字段(isDelete=0)当过滤条件,建立部分索引可把无效文档直接排除在索引外,降低索引体积与扫描行数。
- 组合索引最左前缀原则: Mango 的“$and”查询里,等值字段放最左、范围字段放最右才能命中索引;一旦顺序错位就会触发全量扫描。
- 视图函数副作用:map 里如果 emit 了数组 key,但查询时只传了前缀,会退化成 range-scan;同理,reduce 为 _sum 时若 rereduce 失败也会重扫。
- 国内云盘 IO 抖动:docs_examined 高往往伴随 disk_read_count 飙升,导致同步端阻塞;优化后不仅 QPS 提升,还能减少移动网络下的流量费用。
答案
- 确认查询计划:在 Fauxton → Run Query Explain 里查看 mrargs 的“start_key/end_key”是否收窄;若 start_key=[]、end_key={} 即全表扫描,需立刻建索引。
- 建立高筛选率组合索引:
- 把“等值过滤 + 排序”字段放最左,例如 {“selector”:{“orgId”:“10086”,“status”:“active”,“updateTime”:{“$gte”:“2024-01-01”}}},建索引 ["orgId", "status", "updateTime"];
- 若查询里含 $or,拆成多条独立查询再合并,避免 CouchDB 无法复用单一索引。
- 使用 Partial Index 排除冷数据:
这样已删除或测试租户的数据根本不进索引,docs_examined ≈ docs_returned。{ "index": { "partial_filter_selector": { "isDelete": 0, "orgId": {"$exists": true} }, "fields": ["orgId", "status", "updateTime"] }, "ddoc": "idx_active", "type": "json" } - 覆盖索引减少回表:把需要返回的字段全部塞进索引值或 emit 的 value,例如
emit([doc.orgId, doc.status], {“_id”: doc._id, “name”: doc.name});
查询带 ?include_docs=false 且 reduce=false,CouchDB 直接吐索引内容,磁盘 IO 降到 0。 - 定期压缩与视图清理:国内很多业务写入频繁,旧视图索引碎片会导致范围扫描放大;设置
db_fragmentation=30%自动压缩,并在低峰期执行 view_compaction,保证 B+Tree 扇出系数稳定。 - 监控验证:通过
/_stats/couchdb/httpd/requests_count与/_node/_local/_system的 disk_read_count 对比,确认优化后 docs_examined 下降一个数量级,同时 query_latency_p99 从 800 ms 降到 80 ms 以内即达标。
拓展思考
- 移动边缘节点场景:国内高铁、矿区经常 4G/5G 断续,若 docs_examined 过高会直接把同步流量打爆;可结合 Partial Index + 增量复制过滤器(filter function 里直接 return doc.orgId===“10086”),让边缘 SQLite 只拉取索引命中的 doc,节省 70% 流量费。
- 多租户+权限前缀:对于 SaaS 平台,把 tenantId+userRole 作为索引前缀,查询时必带,可天然隔离数据;同时利用 CouchDB 4.x 的内存映射索引缓存,把热点 tenant 的索引常驻内存,QPS 可再翻 3 倍。
- 视图 vs Mango 取舍:如果业务需要复杂聚合,优先用视图 + reduce=_sum,但要在 map 里 提前裁剪字段,避免 emit 整个 doc;若只是点查,用 Mango 部分索引更轻量,升级 4.x 后可直接走原生 NIF,延迟更低。
- 国产化信创适配:在鲲鹏 ARM 或麒麟 OS 上编译 CouchDB 时,打开 snappy 加速与 NEON 指令集,磁盘扫描的 CPU 耗时下降 15%,可把省下来的算力留给更多并发连接,同等硬件多跑 20% 业务实例。