TTL清理最多延迟60秒,因TTLMonitor后台线程默认每60秒扫描一次且不可设为小于1秒,删除异步、不实时,也不触发change stream。

TTL索引无法准时删除,根本原因不是配置错,而是它压根就不是为“准时”设计的——后台线程每60秒才扫一次,且只在主节点运行。
为什么TTL清理有最多60秒延迟
mongod 内置的 TTLMonitor 后台线程默认每 60 秒触发一轮扫描,这个间隔不可配置为小于 1 秒(4.4+ 版本仍硬编码为 60s)。它不会因为文档刚过期就立刻执行删除。
- 你设了
expireAfterSeconds: 0,字段值是ISODate("2026-09-07T13:27:00Z"),但实际删除可能发生在13:27:59到13:28:59之间的任意时刻 - 如果主节点正在做复制集选举、或刚完成大体积写入,这一轮扫描还可能被跳过或延后
- 日志里看不到“立即删”的记录,
db.serverStatus().metrics.ttl中的passes字段增长就是扫描轮次,不是实时事件流
为什么文档已过期却还在查询结果里出现
常见现象:查 db.collection.find({ expiresAt: { $lt: new Date() } }) 还能拿到数据,但预期已被删。这不一定是 TTL 失效,而是时间判断逻辑和 TTL 行为不匹配。
- TTL 删除是异步后台操作,不阻塞查询;查询时文档物理上还没被移走,所以仍可命中
- 应用层若依赖“过期即不可见”,必须在查询条件中显式加时间过滤:
{ expiresAt: { $gt: new Date() } },不能只靠 TTL 保证可见性 - 如果集合开启了
readConcern: "majority",而 TTL 删除尚未同步到多数节点,从节点读可能短暂看到“已删未同步”的旧文档
为什么改小 expireAfterSeconds 后旧文档没被立刻清理
TTL 不会回溯处理已存在的文档——它只对“字段值 + expireAfterSeconds 小于当前时间”的文档生效。改小参数本身不触发重扫描,也不会让原本不过期的文档突然变过期。
- 例如原索引是
{ expiresAt: 1 }, { expireAfterSeconds: 3600 },你用collMod改成60,只有那些expiresAt + 60s < now()的文档才会被本轮扫描删掉 - 字段值是
ISODate("2026-09-07T13:00:00Z")的文档,在expireAfterSeconds: 3600下本该在14:00过期;改成60后,它在13:01就满足条件,但得等下一轮 TTLMonitor 扫描(最多再等 60 秒)才删 - 没有“强制立刻清理所有过期文档”的命令;想清历史积压,只能手动
deleteMany()或重建索引(注意重建会全表扫描)
真正容易被忽略的是:TTL 清理器不发任何通知,不触发变更流,也不更新 _id 或其他字段。如果你的应用靠监听 change stream 捕获删除事件来联动下游,那这部分逻辑注定会漏掉——得自己加定时轮询或业务层兜底。

















