bigywater.tech
MongoDB · 36

内存排序的 100MB 限制与 allowDiskUse

排序数据超过 100MB 会怎样?这篇用 150 MB 的集合在 allowDiskUseByDefault 开关两种状态下对比,再看 limit 和索引如何避开这个限制。

100 MiB
阻塞排序的内存上限(internalQueryMaxBlockingSortMemoryUsageBytes = 104,857,600)
292
allowDiskUseByDefault 为 false、没传 allowDiskUse 时超限的错误码
2 次
150 MB 排序溢出磁盘的次数(spilledBytes 155,400,000)

限制与参数

实测 · 本机执行输出
  test> db.adminCommand({getParameter: 1, allowDiskUseByDefault: 1, internalQueryMaxBlockingSortMemoryUsageBytes: 1})
  {
    allowDiskUseByDefault: true,
    internalQueryMaxBlockingSortMemoryUsageBytes: Long('104857600'),
    ok: 1
  }
  test> db.big.stats().size / 1024 / 1024 + " MB(big 的 dataSize)"
  147.48573303222656 MB(big 的 dataSize)

阻塞排序(无索引可用的 SORT 阶段)最多用 100 MiB 内存。9.0.2 里 allowDiskUseByDefault 默认为 true,超过就自动溢出磁盘。

关掉默认

实测 · 本机执行输出
  test> db.adminCommand({setParameter: 1, allowDiskUseByDefault: false})
  { was: true, ok: 1 }
  test> db.big.find().sort({k: 1})
  {
    code: 292,
    message: 'Executor error during find command: test.big :: caused by :: Sort exceeded memory limit of 104857600 bytes, but did not opt in to external sorting.'
  }
  test> db.small.find().sort({k: 1})    // 50 MB,没超
  50000

关掉 allowDiskUseByDefault(旧版本的默认行为)后,超过上限报 code 292 did not opt in to external sorting,没超的小集合排序正常。

allowDiskUse

实测 · 本机执行输出
  test> db.big.find().sort({k: 1}).allowDiskUse().explain("executionStats")
  {
    nReturned: 150000,
    ms: 337,
    stages: [
      {
        stage: 'SORT',
        usedDisk: true,
        spills: 2,
        spilledBytes: 155400000,
        memLimit: 104857600
      }
    ]
  }

传 allowDiskUse 后排序溢出磁盘:explain 里 SORT 阶段 usedDisk: true、spills: 2。

耗时

实测 · 本机执行输出
  small(50,000 个):不排序 30 ms;排序 46 ms(内存)
  big(150,000 个):  不排序 97 ms;排序 144 ms(溢出磁盘,约 150 MB 的排序数据)

150 MB 的溢出磁盘排序比不排序多 47 ms;50 MB 的内存排序多 16 ms。

sort + limit

实测 · 本机执行输出
  test> db.big.find().sort({k: 1}).limit(100)    // allowDiskUseByDefault 仍是 false
  100
  test> var e = db.big.find().sort({k: 1}).limit(100).explain("executionStats"); var s = e.executionStats.executionStages; var o = null; while (s) { if (s.stage.includes("SORT")) o = {stage: s.stage, usedDisk: s.usedDisk, limit: s.limitAmount}; s = s.inputStage }; o
  { stage: 'SORT', usedDisk: false, limit: 100 }

带 limit 的排序只保留前 N 个,不会超限,usedDisk: false。

索引

实测 · 本机执行输出
  test> db.big.find().sort({k: 1})    // 有索引,allowDiskUseByDefault 仍是 false
  150000
  test> explain
  {
    plan: 'FETCH > IXSCAN(k_1)',
    nReturned: 150000,
    keysExamined: 150000,
    docsExamined: 150000,
    ms: 87
  }
  有索引的排序读取全部 150,000 个文档(去掉 pad 字段):155 ms(无索引、溢出磁盘:144 ms)
  test> db.adminCommand({setParameter: 1, allowDiskUseByDefault: true})
  { was: false, ok: 1 }

排序字段有索引时没有 SORT 阶段,不受内存上限影响;不过读取全部文档的总耗时不一定更短(155 ms 对 144 ms)。

分步演示

内存排序限制
本步的对象

    本文用到的工具与边界

    • 本机 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、时间戳等每次运行都不同。参数用 setParameter 临时修改并改回;耗时用 pymongo 的 3 次中位数,查询去掉 pad 字段以减少传输
    • 没有验证:aggregate 里 $sort、$group 各自的内存限制与溢出(各 100 MiB);spill 文件的位置(_tmp 目录)与磁盘空间不足时的行为;分片集群上 mongos 合并排序的内存;不同文档大小下 spills 次数与上限的关系;allowDiskUse 对并发的影响
    京ICP备2026067502号-1京公网安备11010502063487号