count() 在分片集群中返回错误数量,因其仅汇总各 shard 的 wiredtiger 元数据快照(如 ns->nrecords),不扫描实际文档;当存在 chunk 迁移、孤儿文档或 balancer 活跃导致元数据与实际数据不一致时,结果不可靠。

count() 为什么在分片集群里返回错数
因为 count() 不扫描文档,只汇总各 shard 的 WiredTiger 元数据快照(比如 ns->nrecords)。这在单机或副本集里没问题,但分片环境下,只要存在以下任一情况,结果就不可信:
- chunk 正在从 Shard A 迁往 Shard B:迁移中源端未删、目标端未写完,该文档可能被两边同时计入
- 迁移中断后残留的「孤儿文档」:路由层找不到它,
count()完全忽略它 - balancer 活跃时,config server 元数据和各 shard 实际数据存在短暂不一致
这不是 bug,是设计选择——用一致性换速度。你看到的波动值(比如同一秒执行两次 count() 得到 1024 和 987),基本就是迁移/孤儿文档在作祟。
聚合 $group + $sum 是最直接的替代方案
db.collection.aggregate([{$group: {_id: null, count: {$sum: 1}}}]) 强制每个 shard 执行本地扫描,再由 mongos 合并,绕过元数据估算,结果准确。但它不是银弹:
- 性能开销大:尤其无索引时,每个 shard 都要全表扫;加
{$match: {}}不能省,否则某些驱动(如 PyMongo)会报TypeError - 内存压力高:大集合可能触发
Exceeded memory limit for $group,需加allowDiskUse: true - 超时风险:没设
maxTimeMS可能卡住整个 mongos 请求队列
推荐写法:db.collection.aggregate([{$group: {_id: null, count: {$sum: 1}}}], {maxTimeMS: 30000, allowDiskUse: true})
countDocuments({}) 看似简洁,但有隐藏陷阱
countDocuments({}) 表面看比聚合“轻”,实际仍走全量路由扫描,且必须满足三个条件才真正可靠:
- filter 不能为
null或省略,必须显式传{},否则驱动行为不一致 - 务必加
hint: "_id_",否则在无其他有效索引时,shard 会退化成 collection scan - 执行前确认
sh.isBalancerRunning()返回false,否则迁移冲突会导致部分 shard 返回 stale 结果
它比聚合少写几个字符,但底层逻辑一样重,且错误配置下更难排查(比如 hint 写错成 "_id" 少了下划线,就 silently 降级)。
真正适合线上业务的计数策略
靠每次请求都扫全量,不是长久之计。生产环境应规避实时精确 count:
- 前端分页总数、后台配额校验这类场景,改用带 TTL 的聚合统计:例如按小时建
stats_hourly集合,用$group + $sum写入,查时读缓存 - 关键业务字段(如用户订单数)可加冗余计数器:在增删文档时用
updateOne(..., {$inc: {order_count: 1}})维护,原子性强、响应快 - 必须临时校准?先停 balancer,再连每个 shard 的 primary 手动跑
db.collection.estimatedDocumentCount()+db.runCommand({cleanUpOrphaned: "db.coll"}),最后统一countDocuments({})
孤立文档不会自己消失,也不会被常规查询命中;它们安静躺在磁盘上,只等某次 count() 把你拉进坑里。











