count()在分片集群中不可信,因仅聚合各shard缓存的元数据,迁移时导致重复或漏数;唯一准确方案是countDocuments({}, {hint: "_id_", maxTimeMS: 30000})。

分片集群里 count() 统计不准确,不是慢,是根本不可信——它压根不扫文档,只拼凑各 shard 的元数据快照,迁移时必然重复或漏数。
count() 在分片集群里到底做了什么
你在 mongos 上调 count(),它不会下发真实查询,而是让每个 shard 直接返回自己内存里缓存的 ns->nrecords 值(WiredTiger 引擎维护的粗略计数),mongos 把这些数字加起来就完事。这个过程极快,但完全绕过事务快照、路由层和实际文档状态。
- 迁移中 chunk 同时存在于源 shard 和目标 shard → 两边都上报,同一文档被算两次
- 迁移中断残留的孤儿文档 → 路由层找不到归属,shard 也不主动上报 → 彻底消失在统计里
- WiredTiger 元数据刷新有延迟 → 即使 balancer 停了,
count()结果也可能滞后几秒到几十秒
为什么 countDocuments({}) 才是唯一能信的方案
countDocuments({}) 是强制走真实游标遍历的路径:每个 shard 在自己的本地上下文里执行带 _id 排序的扫描,mongos 合并结果。它不是“更快的 count()”,而是“带协调的全量扫描”。
- 必须显式传
{},不能省略或传null,否则 PyMongo/Node.js 驱动会直接抛TypeError - 务必加
.hint("_id_"),否则可能 fallback 到 collection scan,尤其当集合没建其他有效索引时 - 设
maxTimeMS: 30000,防止某 shard 卡住拖垮整个请求 - 执行前必须确认
sh.isBalancerRunning()返回false,否则迁移冲突会导致结果乱序甚至中断
别用 estimatedDocumentCount() 冒充精确值
estimatedDocumentCount() 和 count() 实际是一回事,只是 v3.4+ 的新名字。它快,但语义上从不承诺一致性:
- 在分片集群中,它同样吃迁移重复、孤儿遗漏、元数据延迟三重硬伤
- 它不进事务快照,事务内调用依然返回裸眼估算值
- 前端分页总数、配额判断、幂等 key 生成等强语义场景,绝对不能用
聚合 $sum 也不能当 count() 平替
db.collection.aggregate([{$group: {_id: null, count: {$sum: 1}}}]) 确实能绕过元数据路径,得到一致结果,但它没有 countDocuments() 的优化机制:
- 不支持
hint,无法强制走_id索引 - 不自动利用
_id排序,游标效率更低 - 超时控制弱,出问题更难收敛
真正落地时,该用 countDocuments({}, {hint: "_id_", maxTimeMS: 30000}) 就别绕弯;如果业务能接受波动,就明确用 estimatedDocumentCount(),但得清楚代价——它快,是因为它压根没打算准。

















