如何统计缺失率?
解读
在国内 CouchDB 面试中,面试官问“缺失率”通常不是让你写一段通用 SQL,而是想确认三件事:
- 你是否理解 CouchDB 无 Schema、JSON 文档模型带来的“字段空值”与“文档缺失”两种不同语义;
- 你是否会用 Map 函数 + 内置统计 在 Server 端完成计算,而不是把百万级文档拉到客户端;
- 你是否知道 增量视图(_view/update_seq)与分区视图(_partition) 在超大规模数据下如何保持性能,避免一次性全表扫描。
一句话:让 CouchDB 自己把“缺字段 / 字段为 null / 字段为 ''”的文档数算出来,再除以总数,返回一个可缓存、可增量更新的视图结果。
知识点
- Map -only 视图:Map 阶段 emit(key, value) 会持久化到 B+ 树,value 可以是 0/1 标记位,后续用 _stats 内置聚合。
- 内置聚合函数:_stats、_count、_sum 都在 reduce 阶段由 CouchDB 原生 C 实现,比 JavaScript reduce 快一个数量级。
- 空值三态:字段不存在(missing)、字段为 null、字段为 "",在 Map 里需分别用 typeof field === 'undefined'、field === null、field === '' 判断。
- 增量刷新:视图以数据库 update_seq 为版本号,新增/修改文档只会触发增量 Map,TB 级数据也能秒级返回。
- 分区视图(2.x+):若库开启 partitioned:true,可在设计文档里指定 "options": {"partitioned": true},把缺失率统计限定在单个分区,避免跨分区网络开销。
- 客户端二次聚合:如果业务要“按省份统计缺失率”,可在 Map 里 emit(province, flag),再用 _stats 得到每个 province 的 sum 与 count,客户端做 sum/count 即可,无需写 reduce。
答案
设计文档 _design/missing 如下(以统计字段 phone 缺失率为例):
{ "_id": "_design/missing", "views": { "phone_missing": { "map": "function (doc) { if (doc.type !== 'user') return; var missing = (typeof doc.phone === 'undefined' || doc.phone === null || doc.phone === ''); emit(null, missing ? 1 : 0); }", "reduce": "_stats" } } }
调用方式:
GET /mydb/_design/missing/_view/phone_missing?reduce=true&group_level=0
返回示例:
{"rows":[ {"key":null,"value":{"sum":1200,"count":10000,"min":0,"max":1,"sumsqr":1200}} ]}
缺失率 = sum / count = 1200 / 10000 = 12%
该结果会被 CouchDB 缓存,后续只有增量文档需要重新计算,对线上读写几乎无影响。
拓展思考
- 多字段交叉缺失:若业务要“身份证或手机号任一缺失”的比率,可在 Map 里 emit(null, (missingId || missingPhone) ? 1 : 0),仍用 _stats,一次视图即可返回。
- 时间窗口缺失率:在 emit 里把日期截成 YYYY-MM-DD 作为 key,reduce 仍用 _stats,可得到每日缺失率曲线,配合 Grafana 直接绘图。
- 超大规模优化:单库文档过 10 亿时,可开启 clustered view(3.x 默认),让分片在本地先 reduce,再由 coordinator 节点做最终 merge,网络传输量从 N 条文档降到 N 条聚合值。
- 离线场景:CouchDB 同步到 PouchDB 后,手机端仍可调用同一视图,缺失率计算逻辑完全一致,真正做到“离线优先”。