1. 前言

在近期的工作中,使用了MongoDB来保存了一些日志数据,但是这些日志数据具有一定的时效性,也就是按照业务的需要,保存xx天即可,在超过了这个时间之后,需要删除过时的日志。
按照之前的做法,可以使用定时任务,例如:Spring的定时器,xxl-job等工具,在每天凌晨的时候删除数据,这种方法虽然可行,但是需要额外的编码工作。

那有没有一种方式能够简单的搞定过期数据的清理呢?

当然,MongoDB提供了一种TTL索引的机制,可以非常方便的处理这种需求,下面是官方文档,有阅读能力的同学可以直接看这个文档。
《Expire Data from Collections by Setting TTL》

2.概念与使用

TTL的全称为 “Time To Live”,就是存活时间的意思,这是计算机中一个非常常见的概念,经常用于一些需要延时处理的任务,熟悉消息队列的同学肯定知道,延时队列实际上也是TTL的一种实践方式。

结合这个概念,MongoDB通过给数据创建TTL Index 也就是TTL索引的方式,在完成数据的过期和清理的任务。

2.1.使用方式

TTL Index 的使用方法的非常简单,只需要找到一个 日期类型 字段,给这个字段创建索引,并指定存活时间(expireAfterSeconds)就可以了。

我这里使用的MongoDB版本是最新的7.0.6,可以通过db.version();查看当前的版本号,太低的版本可能不支持这个功能。


先准备一条数据,然后创建索引:

db.log_test.insertOne({
   "createTime": new Date(),
   "msg": "Success!"
})

db.log_test.find();

【MongoDB】数据的自动过期,TTL索引-LMLPHP

db.log_test.createIndex({ "createTime": 1 }, { expireAfterSeconds: 10 });

db.log_test.getIndexes();

【MongoDB】数据的自动过期,TTL索引-LMLPHP
这里的expireAfterSeconds设置的是存活时间,单位:秒,在设置完成后,createTime与当前系统时间的差值大于 10s 的数据,将会被清理掉。是不是非常简单。

2.2.数组中包含日期字段

在上面已经创建索引的基础上, 看一下下面这个语句:

db.log_test.insertOne({
   "createTime": [new Date(),'asdf',111],
   "msg": "Success!"
});

【MongoDB】数据的自动过期,TTL索引-LMLPHP
猜一下,这样的数据还能够被自动清理吗?

实际上,这种数据会可以被自动清理的,即使字段值是一个数组,但它包含了日期类型的字段,TTL索引都能够生效。

那再进一步,如果数组中包含了多个日期字段呢?该使用哪一个日期呢?例如:

db.log_test.insertOne({
   "createTime": [new Date,ISODate("2024-04-20T13:48:18.428Z")],
   "msg": "Success!"
});

【MongoDB】数据的自动过期,TTL索引-LMLPHP
这种情况,数据也会在10秒后被删除掉。无关乎日期的大小,只要有任意一个日期满足过期的要求,就可以被删掉。

2.3.设置具体的过期时间点

上面说的都是设置一个数据的存活时间,不是固定的过期时间,如果想要设置固定的过期时间也非常简单,只需要把索引做一下变换。

db.log_test.createIndex({ "expireTime": 1 }, { expireAfterSeconds: 0 });

expireAfterSeconds设置为0,然后在expireTime字段上设置具体的时间就可以了。

2.4.额外的过滤条件

上述的情况都是针对全量的数据做过期处理,但可能有的时候我们可能想保留有些有价值的数据,这部分数据不加入自动过期的逻辑。这个时候可以在创建索引的时候加入部分过滤条件,如下:

db.log_test.createIndex(
    {"createTime": 1},
    {
        name:"canExpireDataTTL",
        partialFilterExpression:{"canExpire":true}, // canExpire=true的字段会过期自动清理
        expireAfterSeconds: 10
    }
);

接下来插入两条数据:

db.log_test.insertOne({
   "createTime": [new Date()],
   "msg": "Success!",
   "canExpire":true
});

db.log_test.insertOne({
   "createTime": [new Date()],
   "msg": "Success!",
   "canExpire":false
});

【MongoDB】数据的自动过期,TTL索引-LMLPHP
按照预期,在10秒后,canExpiretrue的数据将会被清理,false的数据将会保留。验证结果如下:
【MongoDB】数据的自动过期,TTL索引-LMLPHP
【MongoDB】数据的自动过期,TTL索引-LMLPHP

3.总结

TTL索引可以在不添加新的代码逻辑的情况下,非常简单清理已过期的数据,只需要在日期类型的字段上创建TTL Index就可以了,即使这个字段是数组,只要数组中有日期也可能生效。

需要注意的是:过期数据的清理工作是由MongoDB的后台线程周期性执行的,一般这个周期是60s执行一次,如果发现上述的数据在 10秒后还没有被删除的话也不用惊慌,数据将会在60s的周期内被清理掉。

04-19 20:31