bigywater.tech
MongoDB · 21

TTL 索引:过期文档什么时候才真正被删

TTL 索引说“过期时间到了就删”,实际什么时候才真的删?哪些类型的字段会被它删?这篇用 5 秒过期的索引观察实际删除的时刻,再验证字段类型、collMod 和大批量过期。

59.0 s
5 秒过期的文档实际被删的时刻——等到第一次 TTL 线程运行
60.2 s
TTL 线程两次运行相隔的时间(ttlMonitorSleepSecs 默认 60)
200,000
一轮 TTL 删除的文档数(文档很小)

什么时候删

实测 · 本机执行输出
  test> db.getSiblingDB("admin").runCommand({getParameter: 1, ttlMonitorSleepSecs: 1})
  { ttlMonitorSleepSecs: 60, ok: 1 }
  已插入 100 个文档,createdAt = 当前时间;索引 expireAfterSeconds=5
  文档数变化(距插入的秒数, 剩余): [(59.0, 0)]
  TTL 线程的运行时刻(距插入的秒数): [59.0, 119.2];相邻两次相隔 60.2 s
  文档在 5 s 时就已过期,实际在 59.0 s 才被删——等到了第一次 TTL 线程运行

TTL 索引不是定时器,而是后台线程每 ttlMonitorSleepSecs(默认 60 秒)扫描一轮。5 秒过期的文档等到第一轮运行(59.0 s)才被删,第二轮在 119.2 s。

哪些字段

实测 · 本机执行输出
  test> db.adminCommand({setParameter: 1, ttlMonitorSleepSecs: 1})
  { was: 60, ok: 1 }
  一小时前的日期等 4 秒之后,还留下: ['iso字符串', 'null', '日期数组(都没过期)', '未来的日期', '毫秒整数', '缺失']

只有 BSON 日期(以及日期数组)会过期;字符串形式的日期、毫秒整数、null、缺失字段都不会。日期数组里最早的日期到期,文档就会被删。

按文档指定过期时间

实测 · 本机执行输出
  三个文档的过期时间分别是 +3 s、+6 s、+9 s;(距插入的秒数, 剩余数): [(3.1, 2), (6.1, 1), (9.2, 0)]

expireAfterSeconds: 0 配合一个存着过期时刻的字段,每个文档在自己指定的时刻之后才会被删。

collMod

实测 · 本机执行输出
  expireAfterSeconds=3600 时,3 秒后还有 20 个文档(30 秒前创建的还没到 1 小时)
  collMod → {expireAfterSeconds_old: 3600, expireAfterSeconds_new: 2}
  改成 2 秒后 3.5 秒:剩 0 个
  test> db.t4.getIndexes()
  [
    {
      v: 2,
      key: { _id: 1 },
      name: '_id_',
      collation: { locale: 'simple' }
    },
    {
      v: 2,
      key: { c: 1 },
      name: 'c_1',
      expireAfterSeconds: 2,
      collation: { locale: 'simple' }
    }
  ]

用 collMod 的 index 选项可以直接修改已有 TTL 索引的 expireAfterSeconds,已有文档立即按新规则判断。

统计

实测 · 本机执行输出
  test> db.serverStatus().metrics.ttl
  {
    deletedDocuments: Long('125'),
    deletedKeys: Long('251'),
    durationMicros: Long('5812'),
    examinedDocuments: Long('125'),
    examinedKeys: Long('125'),
    invalidTTLIndexSkips: Long('0'),
    lowPriorityTicketAdmissions: Long('0'),
    passes: Long('28'),
    queuedForTickets: Long('0'),
    subPasses: Long('28'),
    ticketAdmissions: Long('111'),
    timeProcessingWithTicketsMicros: Long('12477'),
    timeQueuedForTicketsMicros: Long('0')
  }
  test> db.adminCommand({getParameter: 1, ttlMonitorSleepSecs: 1})    // 重启后
  { ttlMonitorSleepSecs: 60, ok: 1 }

serverStatus().metrics.ttl 给出 TTL 线程运行了多少轮、删了多少文档;ttlMonitorSleepSecs 可以运行时用 setParameter 调整,重启后恢复默认。

大批量

实测 · 本机执行输出
  test> db.adminCommand({setParameter: 1, ttlMonitorSleepSecs: 1})
  { was: 60, ok: 1 }
  (距开始插入的秒数, 剩余文档数): [(0.5, 200000), (1.6, 0), (1.6, 0)]
  TTL 线程运行了 1 轮,共删除 200,000 个文档;从插入完成到删完 1.6 秒

20 万个小文档同时过期,一轮就删完了(约 1.6 s,含统计间隔)。

分步演示

TTL 索引
本步的对象

    本文用到的工具与边界

    • 本机 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、时间戳等每次运行都不同。TTL 线程的运行时刻是每 0.25 秒轮询 serverStatus().metrics.ttl.passes 得到的,精度约 0.25 s;为观察方便,后面几节把 ttlMonitorSleepSecs 调成 1 秒
    • 没有验证:副本集上 secondary 如何复制 TTL 删除(由 primary 执行);大文档或多索引集合上 TTL 删除对前台写入的影响;时序集合与分片集合上的 TTL;expireAfterSeconds 极大值的上限;TTL 线程在 primary 降级时的行为;删除发生时锁与 ticket 的占用
    京ICP备2026067502号-1京公网安备11010502063487号