count()在分片集群中不准,因仅读取各shard元数据估算值,迁移时易重复计数或遗漏孤儿文档;唯一精确方式是count_documents({}),需传空filter、加hint "_id_"、设max_time_ms并停balancer。

Count 操作慢,不是因为写法错,而是你默认在要“精确答案”的同时,又没给 MongoDB 足够快的路径。
count() 在分片集群里根本不算数
你在 mongos 上执行 count(),它不会去扫文档,而是让每个 shard 报一个元数据里的估算值(ns->nrecords),然后加起来。这看起来快,但结果不可信:
- 迁移中 chunk 同时存在于源 shard 和目标 shard,同一份文档被算两次
- 迁移失败残留的孤儿文档,路由层看不见,shard 也不上报,彻底漏计
- WiredTiger 元数据刷新有延迟,即使 balancer 停了,
estimated_document_count()也可能滞后几秒到几十秒
这不是性能问题,是语义失效——它压根就不是为“准”设计的。
count_documents({}) 是唯一能信的,但必须带 hint 和超时
想拿到真实总数,只能走 count_documents({})。但它默认可能扫全表,尤其当集合没建有效索引时:
- 必须显式传空 filter:
{},不能传null或省略,否则 PyMongo 会抛TypeError - 加
hint: "_id_"强制走 _id 索引,避免无索引全表扫描;就算你有其他复合索引,不 hint 也可能选错 - 设
max_time_ms=30000,防止某 shard 卡住拖垮整个请求,超时直接抛OperationFailure - 执行前务必确认
sh.isBalancerRunning()返回false,否则迁移冲突会导致结果乱序甚至中断
为什么 explain 显示 "IXSCAN" 还是慢
即使 explain("executionStats") 里看到 "stage": "IXSCAN",也不代表快。关键看这两个字段:
-
"totalDocsExamined":如果远大于"nReturned"(比如 50000 vs 25),说明索引没覆盖查询条件,MongoDB 还得回表查文档内容来过滤 -
"executionTimeMillis"高但"executionStages.docsExamined"低,可能是网络聚合开销或 mongos 协调瓶颈,尤其在跨多 shard 的count_documents场景
真正快的 count,是连文档都不用碰——靠索引项数量推算。只有所有查询条件都能被单个索引完全覆盖(包括 sort 字段),才可能触发“索引计数优化”,但 MongoDB 不保证这点,count_documents 从不依赖它。
业务里别拿估算值当事实用
estimated_document_count() 只适合监控大盘趋势,比如看集合是否在持续增长。一旦进业务逻辑,立刻翻车:
- 用它做分页总数?前端显示 “共 123456 条”,刷一下变成 123450,用户觉得系统抽风
- 用它判断是否触发清理任务?孤儿文档永远不被统计,永远不清理
- 导出程序按估算值分批拉取?最后一批永远 fetch 不完,卡死
真要兼顾准和快,得换思路:缓存 + 定期异步校准(比如凌晨跑一次 count_documents 更新 Redis),或者改用带时间窗口的聚合($group + $sum)配合 TTL 索引做近实时统计——这些都不是 count 能解决的事。











