TTL索引是MongoDB基于日期字段的异步过期机制,每60秒扫描并清理早于当前时间的文档,非实时删除;仅支持单字段升序Date类型索引,需显式维护expiresAt字段。

什么是TTL索引,它真能自动删数据吗?
TTL索引(Time-To-Live Index)是MongoDB原生支持的过期机制,但它**只对单个日期字段生效,且删除动作由后台线程异步触发,不是实时的**。它不是“定时任务”,也不是数据库级的GC;本质是:MongoDB每隔60秒扫描一次TTL索引对应的日期字段,把值早于当前时间的文档标记为待删除,并在后续空闲时物理清理。
常见误解是“设了TTL就立刻消失”——实际可能延迟几分钟甚至更久,尤其在写入压力大、后台线程繁忙时。所以它适合容忍分钟级延迟的场景,比如会话缓存、临时日志、验证码记录。
如何创建有效的TTL索引?注意字段类型和方向
必须满足三个硬性条件,缺一不可:
-
expireAfterSeconds参数只能作用于Date类型字段(不能是字符串、数字或ISODate字符串) - 索引必须是单字段升序(
{ "createdAt": 1 }),降序或复合索引不被TTL识别 - 该日期字段必须存在于所有目标文档中,且值不能为
null或缺失(否则文档永远不会过期)
正确示例:
db.sessions.createIndex({ "expiresAt": 1 }, { expireAfterSeconds: 0 })
这里 expireAfterSeconds: 0 表示“过期时间就是 expiresAt 字段本身的值”,文档会在 expiresAt 时间点之后被清理。若设为 3600,则表示“从 expiresAt 时间起再加3600秒后过期”——但这种用法容易混淆,建议统一用 0 + 显式写入未来时间。
TTL索引失效的典型原因有哪些?
线上最常踩的坑集中在数据写入和索引维护上:
- 插入文档时漏写
expiresAt字段,或写成字符串如"2025-04-05T10:00:00Z"—— MongoDB不会自动转为Date,该文档永久存活 - 用
updateOne更新其他字段但没重置expiresAt,导致本该续期的会话提前过期 - 集合开启了分片,但TTL索引未在每个分片上单独创建(MongoDB不会自动同步TTL索引)
- 执行了
collMod或重建索引操作,TTL索引被意外删掉而未重新创建
验证是否生效,可查:db.sessions.getIndexes() 看输出里是否有 "expireAfterSeconds": 0;再用 db.sessions.find({ expiresAt: { $lt: new Date() } }).count() 估算当前待删量(注意:这不等于已删数量)。
能不能用TTL替代业务层的过期逻辑?
不能完全替代,尤其当你的场景要求强一致性过期行为时。比如用户登出需立即使token失效、支付订单超时需触发回调通知——这些必须由应用层主动 deleteOne 或标记状态,再配合TTL兜底清理残留。
TTL更适合做“减负”:避免手动轮询清理、降低CRON压力、防止磁盘无限增长。但它不提供钩子、不发事件、不保证顺序、不支持条件过期(例如“30天无操作才过期”就得靠应用更新 expiresAt)。
真正关键的是:把 expiresAt 当作受控字段来维护,而不是丢给TTL“自生自灭”。每次业务逻辑影响生命周期时,都应显式计算并写入新值。











