MongoDB的TTL 索引 (Time-To-Live Index)?
MongoDB 的 TTL 索引 (Time-To-Live Index) 是一种特殊的单字段索引,主要用于在指定时间后自动从集合中删除文档。
这对于处理临时数据(如日志、会话信息、验证码等)非常有用,无需开发人员编写额外的定时任务(Cron Job)来清理数据。
以下是关于 TTL 索引的详细指南:
1. 核心原理
- 基于时间字段: TTL 索引必须创建在包含 Date (BSON Date) 类型值的字段上。
- 后台线程: MongoDB 有一个后台线程(TTL Monitor),默认每 60 秒运行一次。它会扫描 TTL 索引,将文档中的时间字段值与当前时间进行比较,如果过期则删除该文档。
2. 如何创建 TTL 索引
TTL 索引主要有两种使用模式:
模式 A:在创建后的固定时间段后过期
这是最常见的用法。例如,你希望文档在创建后 1 小时(3600秒)自动删除。
步骤:
- 文档中需要有一个字段记录创建时间(例如
createdAt)。 - 创建索引时指定
expireAfterSeconds参数。
javascript
// 1. 插入数据时,createdAt 必须是 Date 类型
db.logs.insert({
"msg": "user login",
"createdAt": new Date()
})
// 2. 创建 TTL 索引,指定 3600 秒后过期
db.logs.createIndex( { "createdAt": 1 }, { expireAfterSeconds: 3600 } )
结果:文档将在 createdAt 时间 + 3600 秒后被删除。
模式 B:在指定的具体时间点过期
如果你希望每个文档的过期时间都不一样(例如:优惠券 A 明天过期,优惠券 B 下个月过期)。
步骤:
- 文档中包含一个字段表示“过期时间点”(例如
expireAt)。 - 创建索引时将
expireAfterSeconds设置为 0。
javascript
// 1. 创建索引,设置 expireAfterSeconds 为 0
db.coupons.createIndex( { "expireAt": 1 }, { expireAfterSeconds: 0 } )
// 2. 插入数据时,直接指定该文档应该被删除的具体时间
db.coupons.insert({
"code": "XYZ123",
"expireAt": new Date("2023-12-31T23:59:59Z") // 在这个时间点删除
})
结果:当当前时间超过 expireAt 字段的值时,文档被删除。
3. 重要限制与注意事项
在使用 TTL 索引前,必须了解以下限制:
- 字段类型限制: 索引字段的值必须是 BSON Date 类型(或者是包含 Date 的数组)。如果字段是字符串(String)、数字(Number)或不存在,文档不会被删除。
- 单字段索引: TTL 属性通常用于单字段索引。虽然可以在复合索引中使用,但 TTL 属性只对该复合索引中的前导字段(且该字段必须是 Date)生效,这通常不推荐,容易混淆。
- 主键限制: 不能在
_id字段上创建 TTL 索引。 - Capped Collections(固定集合): 不能在固定集合上创建 TTL 索引,因为固定集合是根据大小或插入顺序自动覆盖旧数据的。
- 删除延迟(非实时):
- TTL 线程每 60 秒运行一次。
- 如果数据库负载很高,删除操作可能会排队。
- 结论: 文档过期后,可能还会保留在数据库中一小段时间(通常是几秒到几分钟)。不要依赖 TTL 索引来实现秒级精度的业务逻辑。
4. 修改过期时间
如果你已经创建了 TTL 索引,但想修改过期时间(例如从 1 小时改为 24 小时),不需要删除重建索引,可以使用 collMod 命令:
javascript
// 将 logs 集合中 createdAt 索引的过期时间修改为 86400 秒 (24小时)
db.runCommand({
collMod: "logs",
index: {
keyPattern: { createdAt: 1 },
expireAfterSeconds: 86400
}
})
5. 常见应用场景
- 用户 Session/Token: 用户登录后 Token 有效期为 7 天,过期自动清理。
- 短信验证码 (OTP): 验证码 5 分钟内有效,过期自动作废。
- 临时日志/监控数据: 只保留最近 30 天的系统日志,节省磁盘空间。
- 购物车数据: 未结账的购物车保留 24 小时。
总结
MongoDB TTL 索引是管理数据生命周期的神器,它简单、自动化且高效。只要记住必须使用 Date 类型以及删除存在一定延迟这两个关键点,就能很好地利用它。