count()和estimated_document_count()都不准,因二者均跳过文档扫描、仅读取WiredTiger元数据,在分片迁移中易重复计数或遗漏孤儿文档;唯一精确方式是count_documents({}),需显式传空filter、加hint "_id_"、设max_time_ms并确保balancer已停。

count()在分片集群中不准,不是性能问题,而是语义问题:它默认不扫描文档,只查各 shard 的元数据估算值,迁移中极易漏计或重复计数。想快,得接受不准;想准,就得慢——这是硬约束,没法绕开。
为什么 count() 和 estimated_document_count() 都不准
这两个方法本质相同:都跳过实际文档扫描,直接读取 WiredTiger 引擎维护的集合级元数据(如 ns->nrecords)。在分片环境下,mongos 汇总所有 shard 返回的估算值,但:
- 迁移进行中时,源 shard 和目标 shard 可能同时持有同一 chunk 的副本,导致重复计入
- 迁移中断后残留的「孤儿文档」不会被路由层识别,也不会被任何 shard 主动上报,彻底丢失在统计之外
- 即使 balancer 已停,WiredTiger 的元数据刷新有延迟,
estimated_document_count()仍可能滞后真实数量几秒到几十秒
count_documents({}) 是唯一靠谱的精确计数方式
它强制走全量路由 + 协调扫描,每个 shard 独立执行带 _id 排序的游标遍历,并由 mongos 合并结果。虽然慢,但原子、一致、可重复:
- 必须显式传空 filter:
{},不能省略或传null,否则 PyMongo 会报TypeError - 加
hint: "_id_"能避免无索引全表扫描,尤其当集合没建其他有效索引时 - 设
max_time_ms=30000防止长尾阻塞,超时抛OperationFailure而非卡死 - 线上执行前务必确认
sh.isBalancerRunning()返回false,否则迁移冲突风险极高
估算值只适合监控和趋势判断,不能用于业务逻辑
比如用 estimated_document_count() 做分页总数、配额校验、幂等判断,都会出事。典型误用场景:
- 后台任务依赖该值决定是否触发清理 —— 可能漏删孤儿文档
- 前端显示“共 N 条”,N 波动剧烈,用户感知异常
- 导出任务按估算值分批拉取,最后一批永远拉不到头
真正安全的替代方案只有两个:缓存 + 定期异步校准,或者改用带时间窗口的聚合($group + $sum)配合 TTL 索引做近实时统计。
PyMongo 中别写错方法名和参数位置
常见拼写错误和参数陷阱:
-
count_documents是下划线,不是驼峰 ——countDocuments在 PyMongo 里根本不存在 -
estimated_document_count()不接受 filter 参数,传了会直接报TypeError: estimated_document_count() takes no arguments -
max_time_ms必须放在 keyword 参数里,写成 positional 会进错参数槽位,静默失效 - 使用 session 时,
count_documents支持session=...,但estimated_document_count不支持
最易被忽略的是:估算值在单机或副本集上也未必准 —— 它依赖存储引擎元数据,而 WiredTiger 的 nrecords 在 bulk write 回滚、未 checkpoint 的 crash recovery 后可能未及时修正。











