bigywater.tech
MongoDB · 47

count、countDocuments、estimatedDocumentCount 的差别

count、countDocuments、estimatedDocumentCount 到底差在哪?各自执行什么?哪个会不准?这篇在 100 万个文档上量耗时、看 explain,还试了 kill -9 之后的计数。

0.2 对 97 ms
estimatedDocumentCount() 与 countDocuments({}) 在 100 万个文档上的耗时
0
kill -9 重启后 estimatedDocumentCount 的值(countDocuments 是 298,500)
1.4 对 110 ms
带过滤条件的 countDocuments:条件字段有索引与没有索引

三种写法的耗时

实测 · 本机执行输出
  estimatedDocumentCount()              0.2 ms   = 1,000,000
  count_documents({})                    97 ms   = 1,000,000
  count_documents({a: 5})(a 有索引)        1.4 ms   = 10,000
  count_documents({b: 5})(b 无索引)        110 ms   = 1,000
  命令 count({})(旧)                       0.5 ms   = 1,000,000
  命令 count({a: 5})(旧)                   1.7 ms   = 10,000

estimatedDocumentCount 读集合元数据,0.2 ms;countDocuments({}) 要数全部文档,97 ms;带条件的 countDocuments 取决于有没有索引(1.4 ms 对 110 ms)。

各自执行什么

实测 · 本机执行输出
  test> db.t.explain().count({a: 5})
  { stage: 'COUNT', input: 'COUNT_SCAN' }
  test> db.t.explain().count({})
  { stage: 'RECORD_STORE_FAST_COUNT', fast: undefined }
  test> countDocuments({a: 5}) 内部是 aggregate [{$match: {a: 5}}, {$group: {_id: 1, n: {$sum: 1}}}]
  {
    plan: 'GROUP > COUNT_SCAN(a_1)',
    nReturned: 1,
    keysExamined: 10000,
    docsExamined: 0,
    ms: 1
  }
  test> countDocuments({b: 5}) 内部的 aggregate
  {
    plan: 'GROUP > COLLSCAN',
    nReturned: 1,
    keysExamined: 0,
    docsExamined: 1000000,
    ms: 121
  }
  test> $collStats {count: {}}
  [
    {
      ns: 'test.t',
      host: 'host1:9901',
      localTime: ISODate('2026-10-12T02:44:31.115Z'),
      count: 1000000
    }
  ]

旧 count 命令不带条件是 RECORD_STORE_FAST_COUNT;countDocuments 内部是 aggregate,有索引时 GROUP > COUNT_SCAN,没有就 GROUP > COLLSCAN。

什么时候会不准

实测 · 本机执行输出
  SIGKILL 之前:estimatedDocumentCount = 300,000,countDocuments = 300,000
  重启之后:estimatedDocumentCount = 0,countDocuments = 298,500
  validate 之后:estimatedDocumentCount = 298,500(validate 返回 nrecords = 298,500)

元数据计数在 kill -9 后重启时是 0,对集合做 validate 才修正;countDocuments 不受影响。(本实验里是新建集合、没有经历过 checkpoint 的情形。)

删除之后

实测 · 本机执行输出
  delete_many({a: {$lt: 50}}) 之后:estimatedDocumentCount = 500,000,countDocuments = 500,000

正常运行时,删除大量文档后元数据计数立即更新。

limit 与 distinct

实测 · 本机执行输出
  count_documents({}, limit=1000):0.4 ms;不带 limit:51 ms
  count_documents({a: 60}, limit=1000):0.5 ms;不带 limit:1.6 ms
  test> db.t.distinct("a").length    // a 有索引
  50
  distinct("a") 耗时 0.6 ms;distinct("b")(无索引)耗时 120 ms

countDocuments 带 limit 数到上限就停;distinct 在有索引的字段上也很快(0.6 ms)。

用法

实测 · 本机执行输出
  test> db.t.count()    // mongosh 旧方法
  DeprecationWarning: Collection.count() is deprecated. Use countDocuments or estimatedDocumentCount.
  500000
  test> db.t.count({a: 60})
  DeprecationWarning: Collection.count() is deprecated. Use countDocuments or estimatedDocumentCount.
  10000

总数用 estimatedDocumentCount,带条件的精确值用 countDocuments;旧的 count() 已弃用。

分步演示

count 的三种写法
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / MongoDB 9.0.2(Homebrew 的 mongod、mongos)/ mongosh 2.12.0 / pymongo 4.18.3;每个实验在临时目录里起全新的 mongod(独立 dbpath,端口 9900 以上,只监听 127.0.0.1,WiredTiger 缓存 0.25 GB),需要副本集或分片集群时在本机起多个进程;命令输出来自真实的 mongosh,批量写入、计时与多线程用 pymongo;计时用 time.perf_counter,数字每次运行会有小幅抖动,ObjectId、时间戳等每次运行都不同。100 万个文档;耗时是 pymongo 的 5 次中位数;kill -9 部分每次运行丢失的文档数不同(这次 1,500 个)
    • 没有验证:副本集上 estimatedDocumentCount 在 secondary 上的准确性;事务里 estimatedDocumentCount 的限制(需要副本集);分片集群上孤儿文档对 count 的影响;已经历过 checkpoint 的集合在 kill -9 后元数据计数的表现;validate 的耗时与锁
    京ICP备2026067502号-1京公网安备11010502063487号