注意
如果您为了节省存储成本而删除文档,可以考虑 MongoDB Atlas 中的 Online Archive。Online Archive 可自动将不常访问的数据存档到完全托管的 S3 存储桶,以实现经济高效的数据分层。
TTL 索引是特殊的单字段索引,MongoDB 可以在一定时间后或在特定时钟时间使用这种索引自动从集合中删除文档。数据过期对于某些类型的信息(例如机器生成的事件数据、日志和会话信息)很有用,这些信息仅需要在数据库中保留有限的时间。
您可以 在 UI 中为 MongoDB Atlas 托管的部署创建和管理 TTL 索引。
创建 TTL 索引
警告
创建 TTL 索引后,可能需要一次性删除大量符合条件的文档。如此大的工作负载可能会导致服务器出现性能问题。为了避免此类问题,请计划在非工作时间创建索引,或者在为将来的文档创建索引之前,批量删除符合条件的文档。
要创建 TTL 索引,请使用 createIndex()
。指定一个日期类型或包含日期类型值的数组的索引字段。使用 expireAfterSeconds
选项指定 TTL 值(以秒为单位)。
TTL 索引 expireAfterSeconds
值必须介于 0
和 2147483647
(包含两者)之间。
例如,要在 eventlog
集合的 lastModifiedDate
字段上创建 TTL 值为 3600
秒的 TTL 索引,则在 mongosh
中使用以下操作:
db.eventlog.createIndex( { "lastModifiedDate": 1 }, { expireAfterSeconds: 3600 } )
在时间序列集合上创建 TTL 索引
重要
从 MongoDB 7.0 开始,您可在时间序列集合上创建部分 TTL 索引。当您创建时间序列集合时,可以包含可选的 expireAfterSeconds
字段,该字段会在指定时间后删除文档。
例如,创建以下天气数据时间序列集合,并将 expireAfterSeconds
字段设置为在 24 小时后删除文档:
db.createCollection( "weather24h", { timeseries: { timeField: "timestamp", metaField: "sensor", granularity: "hours" }, expireAfterSeconds: 86400 } )
您还可以在带有 partialFilterExpression
字段的集合上创建 TTL 索引。这些索引使用集合的 timeField
作为键字段。您还需要指定:
一个
expireAfterSeconds
字段,用于为文档子集设置明确的过期时间。即使您在创建集合时设置了expireAfterSeconds
字段,partialFilterExpression
字段也允许您为匹配文档的子集设置不同的和更短的过期期限。在
metaField
上的部分过滤器表达式。partial filter expression
定义了将在expireAfterSeconds
字段所定义的时间内过期的文档集。
以下 TTL 索引匹配“weather24h”集合中的文档,其中“sensor”字段设置为“MDB_NYC”,并在 1 小时后将其删除,覆盖我们在创建集合时设置的默认 24 小时:
db.weather24h.createIndex( { "timestamp": 1 }, { expireAfterSeconds: 3600, partialFilterExpression: { "sensor": { $eq: "MDB_NYC" } } } )
重要
如果集合的 expireAfterSeconds
值小于部分 TTL 索引的 expireAfterSeconds
,则集合会在较短的时间后删除文档,因此 TTL 索引不起作用。
将非 TTL 单字段索引转换为 TTL 索引
从 MongoDB 5.1 开始,您可以将 expireAfterSeconds
选项添加到现有单字段索引。要将非 TTL 单字段索引更改为 TTL 索引,使用 collMod
数据库命令:
db.runCommand({ "collMod": <collName>, "index": { "keyPattern": <keyPattern>, "expireAfterSeconds": <number> } })
以下示例将具有模式 { "lastModifiedDate": 1 }
的非 TTL 单字段索引转换为 TTL 索引:
db.runCommand({ "collMod": "tickets", "index": { "keyPattern": { "lastModifiedDate": 1 }, "expireAfterSeconds": 100 } })
更改 TTL 索引的 expireAfterSeconds
值
要更改 TTL 索引的 expireAfterSeconds
值,请使用 collMod
数据库命令:
db.runCommand({ "collMod": <collName>, "index": { "keyPattern": <keyPattern>, "expireAfterSeconds": <number> } })
以下示例更改 tickets
集合上具有模式 { "lastModifiedDate": 1 }
的索引的 expireAfterSeconds
值:
db.runCommand({ "collMod": "tickets", "index": { "keyPattern": { "lastModifiedDate": 1 }, "expireAfterSeconds": 100 } })
行为
数据过期
TTL 索引在索引字段值后经过指定的秒数后使文档过期。过期阈值为已编入索引的字段值加上指定的秒数。
如果字段为数组,且索引中有多个日期值,MongoDB 会使用数组中最低(最早)日期值计算过期阈值。
对于时间序列集合,当存储桶中的所有文档都过期时,TTL 索引也会删除该桶中的数据。这等于存储桶的时间戳上限加上 expireAfterSeconds
值。例如,如果存储桶涵盖 2023-03-27T18:29:59Z
之前的数据且 expireAfterSeconds
为 300,则 TTL 2023-03-27T18:34:59Z
索引会在以下项后使存储桶过期。
如果文档中的索引字段不包含一个或多个日期值,则该文档不会过期。
如果文档不包含索引字段,则文档将不会过期。
删除操作
mongod
中的后台线程会读取索引中的值,并从集合中删除过期文档。
TTL 线程执行的正在进行的删除操作出现在 db.currentOp()
输出中。随着 TTL 线程删除文档,metrics.ttl.deletedDocuments
服务器状态指标会增加。
从 MongoDB 6.1 开始:
删除进程
TTL 后台删除进程会检查每个 TTL 索引中是否存在过期文档。对于每个 TTL 索引,背景进程会删除文档,直到满足以下条件之一:
该进程从当前索引中删除 50000 个文档。
该进程花费一秒钟从当前索引中删除文档。
所有过期的文档都会从当前索引中删除。
然后,该进程移动到下一个索引。进程遍历每个 TTL 索引一次后,当前子遍历完成,新的子遍历开始检查剩余的过期文档。当 TTL 监控从所有 TTL 索引中删除所有可能的候选文档时,遍历完成。
重要
TTL 删除过程是一个单线程后台任务,这意味着 TTL 删除不是并发的,并且在高工作负载或处理大量过期文档时可能需要更长时间。
此外,进程每隔 60 秒就会停止当前的删除循环,以避免在一次大删除上耗费过多时间。发生这种情况时,当前 sub-pass 结束,新的 sub-pass 开始。
遍历和子遍历分别在 metrics.ttl.passes
和 metrics.ttl.subPasses
服务器状态指标中被跟踪。
删除操作的时间
索引在主节点上完成构建后,MongoDB 会立即开始删除过期文档或时间序列存储桶。有关索引构建过程的更多信息,请参阅在填充集合上构建索引。
TTL 索引不保证过期数据会在过期后立即删除。文档过期时间和 MongoDB 从数据库中删除文档的时间之间可能存在延迟。
删除过期文档的后台任务每 60 秒运行一次。因此,在文档过期和后台任务运行之间的时间段内,文档可能会保留在集合中。MongoDB 在索引完成后 0 到 60 秒开始删除文档。
由于删除操作的持续时间取决于 mongod
实例的工作负载,因此过期数据可能会在后台任务运行之间的 60 秒间隔时间以后存在一段时间。
由 TTL 任务启动的删除操作在前台运行,就像其他删除操作一样。
副本集
在副本集成员上,TTL 后台线程只在成员处于主状态时删除文档。当成员处于从状态时,TTL 后台线程处于空闲状态。从成员复制主成员的删除操作。
支持查询
TTL 索引支持查询的方式与非 TTL 索引相同。
独立模式运行的 mongod
当 mongod
在独立运行模式运行且 system.local.replset
集合包含数据时, TTL 监控会停止。如果将副本集节点从副本集中取出并将其作为独立运行节点运行,则 TTL 监控将被禁用。
TTL和模式验证
尽管TTL索引不需要模式验证,但使用验证可以通过标准化用于过期的日期字段的存在和格式来帮助确保一致的行为。
示例,您可以使用模式验证来实施lastModifiedDate
字段的存在,并确保其值遵循有效的日期格式:
db.createCollection( "eventlog", { validator: { $jsonSchema: { bsonType: "object", required: [ "lastModifiedDate" ], properties: { lastModifiedDate: { bsonType: "date", description: "Must be a valid date." } } } } } )
此模式验证规则可确保:
eventlog
集合中的每个文档都包含lastModifiedDate
字段。lastModifiedDate
字段包含有效的日期值。
提示
限制
TTL 索引是单字段索引。复合索引不支持 TTL,并且会忽略
expireAfterSeconds
选项。_id
字段不支持 TTL 索引。从 MongoDB 7.0开始,您可以在时间序列集合的
metaField
上创建一个部分 TTL 索引。在早期 MongoDB 版本中,您只能为时间序列集合的timeField
创建 TTL 索引。不能使用
createIndex()
更改现有索引的expireAfterSeconds
值。请使用collMod
数据库命令。有关详情,请参阅更改 TTL 索引的expireAfterSeconds
值。如果某个字段已存在非 TTL 单字段索引,则无法对同一字段创建 TTL 索引,因为无法创建具有相同键规范且仅选项不同的索引。要将非 TTL 单字段索引更改为 TTL 索引,请使用
collMod
数据库命令。