ttl索引生效需同时满足三条件:字段为date类型、单字段升序索引、字段值非null且存在;否则文档永不删除,常见失效原因是字段存为字符串或数字而非date对象。

MongoDB 的 TTL 索引能自动删除过期文档,但必须满足三个硬性条件:字段是 Date 类型、索引是单字段升序、且文档中该字段值不可为 null 或缺失 —— 不符合任一条件,文档就永远不会被清理。
为什么 expireAfterSeconds 设了却没删数据?
最常见的失效原因是字段类型不对。TTL 索引只识别 Date 对象,"2024-01-01T00:00:00Z" 这种字符串或数字时间戳(如 1704067200)完全无效。
- 检查字段类型:
db.collection.findOne({}).yourDateField.constructor.name,结果必须是Date - 如果存的是字符串,得用聚合管道批量转成
Date:db.collection.updateMany({ yourDateField: { $type: "string" } }, [ { $set: { yourDateField: { $toDate: "$yourDateField" } } } ]) - 确保字段存在且非空:TTL 清理器跳过
yourDateField: null或根本没这个 key 的文档
如何创建真正生效的 TTL 索引?
必须用 createIndex() 显式声明,不能靠 schema 工具或 ORM 自动推导。升序(1)是强制要求,降序(-1)会静默失败。
- 正确写法:
db.collection.createIndex({ "createdAt": 1 }, { expireAfterSeconds: 3600 }) - 错误写法:
db.collection.createIndex({ "createdAt": -1 }, { expireAfterSeconds: 3600 })(不报错但不生效) - 复合索引不支持 TTL:
{ "status": 1, "createdAt": 1 }加expireAfterSeconds会被忽略 - 索引创建后,后台每 60 秒扫描一次过期文档;延迟不是实时的,别指望秒级精确
过期时间怎么算?从哪个时间点开始倒计时?
TTL 的「过期时间」= 文档中日期字段值 + expireAfterSeconds。MongoDB 不看文档插入时间,只认你存进去的那个 Date 值。
- 想让文档插入后 1 小时过期?插入时就得设
createdAt: new Date(),再建索引{ expireAfterSeconds: 3600 } - 想让日志在生成后 7 天过期?字段值应为日志事件发生时刻(比如
eventTime: ISODate("2024-05-01T10:20:00Z")),索引设expireAfterSeconds: 604800 - 如果字段值是未来时间(比如
expiresAt: tomorrow),那文档会在那个未来时间点 +expireAfterSeconds后才删 —— 容易误判成“一直不删”
最容易被忽略的是:TTL 清理器只运行在主节点,副本集里从节点不会主动删;而且它不触发任何钩子或变更流事件,下游系统如果依赖“删完立刻感知”,得自己加轮询或时间字段监控。











