ttl索引由mongod后台线程自动触发删除,需满足三个条件:createdat字段为顶层date类型、索引方向为1且指定expireafterseconds、mongod服务正常运行;否则静默失效。

TTL索引能自动删数据,但不是“定时任务”,而是由mongod后台线程扫描触发——只要索引建对、字段类型正确、时间值有效,它就会自己工作,不依赖Node.js进程是否运行。
createIndex() 必须指定 expireAfterSeconds 和升序方向
调用 collection.createIndex() 时,两个参数缺一不可:{ createdAt: 1 }(方向必须是 1,-1 不支持 TTL)和 { expireAfterSeconds: 604800 }(7 天 = 7 × 24 × 3600 秒)。expireAfterSeconds 是从文档该字段时间起算的秒数,不是“距现在多少秒”。
常见错误:
- 误写成
{ createdAt: -1 }→ 索引创建成功,但 TTL 不生效 - 把
expireAfterSeconds当作绝对时间偏移(比如设为3600却期望“1 小时后统一删除”)→ 实际是每个文档各自倒计时 - 字段名拼错(如
created_atvscreatedAt)→ 索引存在,但没文档匹配,看起来“没反应”
createdAt 字段必须是 Date 类型且非空
TTL 索引只认原生 Date 对象,字符串格式如 "2025-01-01T00:00:00Z" 会被忽略。插入时必须用 new Date() 或 BSON Date 实例。
验证方式:
-
db.logs.findOne({ createdAt: { $exists: true } })→ 检查字段值是不是ISODate(...) -
typeof doc.createdAt === 'object' && doc.createdAt instanceof Date(在 Node.js 中) - 如果字段是嵌套的(如
metadata.createdAt),TTL 不支持 → 必须提到顶层
删除不是实时的,延迟通常在 1–2 分钟内
mongod 默认每 60 秒扫描一次 TTL 索引,检查过期文档并删除。这意味着:
- 一个
createdAt值为 2026-07-21T15:00:00Z 的文档,在expireAfterSeconds: 300下,最早可能在 15:05:00 后被删,但实际可能到 15:05:59 才完成 - 高负载时扫描可能被延迟,不要依赖精确秒级时效
- 不会阻塞写入或查询,但批量过期可能引发短暂 I/O 上升
首次建索引可能触发全表扫描,务必避开高峰期
如果集合已有千万级文档,createIndex() 会同步构建索引,期间可能占满磁盘 IO 和 CPU,导致查询变慢甚至超时。
安全做法:
- 先用
db.logs.countDocuments({ createdAt: { $exists: true, $type: 9 } })确认待索引文档量 - 在业务低峰期执行(比如凌晨 2–4 点)
- 若已有大量过期数据,可先手动删一批:
db.logs.deleteMany({ createdAt: { $lt: new Date(Date.now() - 604800000) } }),再建索引 - 建完立刻用
db.logs.getIndexes()确认输出里含"expireAfterSeconds": 604800
真正容易被忽略的是:TTL 不是“开关式”功能,它依赖字段值 + 索引定义 + mongod 后台线程三者同时成立。少一个环节,就静默失效——而日志里通常不报错,只能靠主动验证。











