count()在分片集群中不准而非慢,因只读元数据快照,迁移时导致重复计数或孤儿文档遗漏;精确计数唯一方案是countdocuments({}),需显式传{}、加hint "_id_"、设max_time_ms并确保balancer已停。

count() 在分片集群里根本不是“慢”,而是“不准”
它压根不扫描文档,只让每个 shard 报个元数据快照(比如 WiredTiger 里的 ns->nrecords),mongos 汇总就完事。这在迁移中会直接崩:同一 chunk 正从 Shard A 迁到 Shard B,A 没删干净、B 没写完,两边都算一遍,就重复;迁移中断后残留的文档路由不到,谁也不认,就成孤儿——count() 对这两种情况完全无感。
常见错误现象包括:
-
count()结果比find().toArray().length少几十或几百条 - 同一集合反复执行,返回值来回跳变
- 刚停 balancer,
count()还是和前一秒不一样
countDocuments({}) 是唯一能落地的精确方案
它强制每个 shard 执行本地 countDocuments,走真实游标遍历(默认按 _id 排序),mongos 合并结果。这不是“更快的 count”,而是“带协调的全量扫描”。
但必须注意几个硬性条件:
- filter 不能省略或传
null,必须显式写{},否则 PyMongo 会抛TypeError - 加
hint: "_id_",避免没索引时触发全表扫描 - 设
max_time_ms=30000,防止某 shard 卡死拖垮整个请求 - 执行前确认
sh.isBalancerRunning()返回false,否则迁移冲突风险极高
estimated_document_count() 和 count() 实际是一回事
二者都跳过文档扫描,直读元数据,estimated_document_count() 只是 count() 的 v3.4+ 替代名,语义没变。它快,但依然漏孤儿、吃迁移波动,不能用于业务逻辑。
典型误用场景:
- 用它做分页总数,前端显示 “共
N条”,N 每次刷新都不一样 - 后台任务靠它判断是否触发清理,结果孤儿文档永远留着
- 导出程序按该值分批拉取,最后一片永远拉不满
这类需求要么缓存 + 定期异步校准,要么改用 aggregate([{$group: {_id: null, count: {$sum: 1}}}]) 配合 TTL 索引做近实时统计。
聚合 $sum 虽然准,但别当 count() 的平替来用
db.collection.aggregate([{$group: {_id: null, count: {$sum: 1}}}]) 确实能绕过元数据路径,得到一致结果,但它没有 countDocuments 的优化机制:不支持 hint、不自动利用 _id 索引排序、超时控制也更弱。
实际线上选型时:
- 要精度且能接受延迟 → 用
countDocuments({}, {hint: "_id_", max_time_ms: 30000}) - 只看趋势、监控用量 → 用
estimated_document_count() - 需要带条件的精确计数(如
{status: "done"})→ 只能用countDocuments(filter),聚合没优势
最常被忽略的一点:哪怕你用了 countDocuments,如果集合上有未清理的孤儿文档,它也会如实计入——那说明问题不在 count 方法,而在分片状态本身。得先连各 shard 执行 db.runCommand({cleanUpOrphaned: "db.collection"}),再跑 count。











