0%

MongoDB TTL索引实战

背景

分享短链记录表需要定期做清除,由于数据量较大,而且对数据库操作的次数很多,原来使用定时任务的方式来删除,会对数据库造成短时内的性能波动

所以打算使用其他方式来处理这个问题

TTL索引

TTL全称是(Time To Live),TTL索引能对一个单列配置过期属性来实现对文档的自动过期删除,我们可以在对字段创建索引时添加expireAfterSeconds选项将索引转换为TTL索引,该字段需要是date类型

在以下几种场景下即使索引设置了expireAfterSeconds属性也不会生效

  • 如果该字段不是date类型,则文档不会过期
  • 如果文档没包含索引的这个字段,则文档不会过期

TTL索引的运行逻辑

  • MongoDB会开启一个后台线程读取该TTL索引的值来判断文档是否过期,TTL值是建立在Date类型的数据上,所以MongoDB可以很轻易的找到过期的数据
  • 但不会保证已过期的数据会立马被删除,因后台线程每60秒触发一次删除任务,且如果删除的数据量较大,会存在上一次的删除未完成,而下一次的任务已经开启的情况,导致过期的数据也会出现超过了数据保留时间60秒以上的现象
  • 对于副本集而言,TTL索引的后台进程只会在primary节点开启,在从节点会始终处于空闲状态,从节点的数据删除是由主库删除后产生的oplog来做同步
  • TTL索引除了有expireAfterSeconds属性外,和普通索引一样,都可以提升搜索速度

TTL索引的限制

  1. 只支持对单个字段创建TTL索引,复合索引不支持expireAfterSeconds选项
  2. _id列不支持TTL索引
  3. 固定集合(capped collection)不支持TTL索引
  4. 不支持用createIndex() 修改expireAfterSeconds属性,但可以用collMod命令修改,或者重建索引,但重建对于大集合成本较高,建议用collMod方式
  5. 一个列只能创建普通索引或TTL索引,不能同时对一个列创建这2种类型索引(实际TTL索引本身就是普通索引,只是多了一个过期属性)
  6. 如果一个列已经存在索引,则需要先将该索引drop后才能重建为TTL索引,不能直接转换

语法规则

使用createIndex()语句创建索引,只是多了一个参数

1
2
// new Date().getTime() - createdAt >=3600 则认为过期,并删除
db.getCollection('user').createIndex({ "createdAt": 1 }, { expireAfterSeconds: 3600 })

如果需要特定在某个时间定过期

该场景是在创建索引时将expireAfterSeconds设置为0,在这种情况下由索引字段的数值来决定文档何时过期,这种场景更加精细化,可灵活的控制文档的过期时间及控制在业务低峰期触发文档过期

1
2
3
// 在时间到达文档里expireAt的时候后判定过期,并删除
// expireAt必须是个Date类型的字段,比如new Date('2020-12-08 18:00:00')
db.getCollection('user').createIndex({ "expireAt": 1 }, { expireAfterSeconds: 0 })

实际操作

1
2
// 30天后自动删除
db.getCollection('shortLinks').createIndex({ 'createdAt': 1 }, { expireAfterSeconds: 60*60*24*30})