事务内count()不准,因其走元数据估算路径,不读取事务快照;countDocuments({})才真正进入事务上下文,但需确保balancer未运行。

事务里用 count() 统计,结果大概率不准——这不是事务本身的问题,而是它默认走元数据估算,压根没进事务快照。
为什么事务中 count() 不受事务隔离影响
事务里的 count() 仍走的是「快速元数据路径」,不触发文档扫描,也不读取当前事务的 snapshot。它汇总的是各 shard 当前最新的元数据(比如 ns->nrecords),而这些值:
- 不受事务 readConcern: "snapshot" 约束
- 不感知事务内已插入但未提交的文档
- 不过滤事务外已删除但尚未清理的孤儿文档
所以你在事务里执行 db.coll.count(),拿到的其实是“集群此刻裸眼看到的估算数”,和事务语义完全脱钩。
countDocuments({}) 才真正进入事务上下文
只有 countDocuments({}) 会下发一个真实查询,强制每个 shard 在自己的事务快照里执行带 _id 排序的游标遍历,并由 mongos 合并结果。这意味着:
- ✅ 包含事务内已插入、未提交的文档(若事务隔离级别支持)
- ✅ 排除事务外已删除、但仍在磁盘上的孤儿文档(因路由层能识别归属)
- ✅ 尊重 readConcern 和 writeConcern 配置
但必须注意:
- {} 不能省略或写成 null,否则 PyMongo/Node.js 驱动会直接报 TypeError
- 建议加 hint: "_id_",避免无索引时 fallback 到全表扫描
- 设 maxTimeMS: 30000,防止长尾拖垮整个事务生命周期
分片集群下事务 + count 的典型误用
常见错误场景包括:
- 在事务里用 count() 判断是否达到配额上限,然后决定是否拒绝写入 —— 实际可能漏判或误判
- 用 estimatedDocumentCount() 做幂等检查,认为“已有 N 条”就跳过初始化 —— N 是迁移中的抖动值,不可信
- 把事务内 count() 结果缓存到 Redis,用于后续分页总数展示 —— 缓存的是过期估算,不是事务态快照
真正安全的做法只有一条:事务内所有需要强一致的计数,必须用 countDocuments({}),且接受它带来的延迟和资源开销。别试图用 aggregate([{$group:{_id:null,count:{$sum:1}}}]) 替代——它同样不进事务快照,只是换了个慢一点的估算方式。
最易被忽略的一点:即使你用了 countDocuments({}),如果 balancer 正在运行,迁移冲突仍可能导致部分 shard 返回临时不一致结果。所以事务内做精确计数前,sh.isBalancerRunning() 必须为 false——这不是建议,是硬性前提。











