全部产品
Search
文档中心

云数据库 MongoDB:MongoDB TTL 索引使用最佳实践

更新时间:Sep 16, 2026

TTL(Time-To-Live)索引是 MongoDB 提供的一种特殊单字段索引,可在文档过期后自动删除。本文介绍 TTL 索引的工作原理、创建与管理方法、使用限制、常见问题与解决方案,以及在云数据库 MongoDB 版上的最佳实践建议。

概述

TTL(Time-To-Live)索引是 MongoDB 提供的一种特殊单字段索引,能够在文档达到指定过期时间后自动将其删除。它适合管理具有明确生命周期的数据,例如日志记录、会话信息、临时缓存、验证码等场景。通过合理使用 TTL 索引,可以有效控制集合的数据量,避免存储空间无限增长。

在生产环境中,TTL 索引的不当使用可能引发多种问题,包括 CPU 周期性飙高、过期数据未被及时删除、磁盘空间持续增长等。本文将从 TTL 索引的工作原理、使用方法、常见问题及最佳实践等方面进行全面介绍,帮助您在云数据库 MongoDB 版上正确使用 TTL 索引。

TTL 索引的工作原理

基本机制

每个 mongod 进程启动时,会创建一个名为 TTLMonitor 的后台线程,该线程默认每隔 60 秒发起一轮 TTL 清理操作。每轮操作的流程如下:

  1. 搜集当前数据库中所有的 TTL 索引。

  2. 依次对每个 TTL 索引生成执行计划并执行数据清理。

  3. 删除索引字段值加上 expireAfterSeconds 小于当前时间的文档。

过期阈值的计算方式为:索引字段的日期值 + expireAfterSeconds 的秒数。如果该结果小于当前时间,则文档被认为已过期。

副本集中的行为

在副本集架构中,TTL 后台线程仅在 Primary 节点上执行删除操作。Secondary 节点的 TTL 线程处于空闲状态,通过复制 Primary 的 oplog 来同步删除操作。这意味着 TTL 删除会产生额外的 oplog,可能影响副本集的同步延迟。

版本差异:批量删除优化(MongoDB 7.0+)

从 MongoDB 7.0 开始(对应开发版本 6.1 引入的特性),TTL 删除操作引入了"公平删除"机制,通过类似"时间片"的方式为每个 TTL 索引分配删除时间,避免某些集合的过期数据长期得不到清理。主要改进包括:

  • ttlMonitorBatchDeletes:启用批量删除模式,默认开启。开启后 TTL 删除会更加公平地分配到各个集合。

  • ttlIndexDeleteTargetTimeMS:每个 TTL 索引每轮删除的时间上限,默认 1000 ms。

  • ttlIndexDeleteTargetDocs:每个 TTL 索引每轮删除的文档数上限,默认 50000。

  • ttlMonitorSubPassTargetSecs:子轮次遍历 TTL 索引的时间上限,默认 60 秒。

这些参数在执行计划中体现为 BATCHED_DELETE 阶段,可通过 explain 命令查看。

创建和管理 TTL 索引

创建 TTL 索引

使用 createIndex 方法,在日期类型字段上创建 TTL 索引。

示例 1:创建 TTL 索引,文档在 lastModifiedDate 字段值的 3600 秒后过期。

db.eventlog.createIndex(
  { "lastModifiedDate": 1 },
  { expireAfterSeconds: 3600 }
)

示例 2:指定时刻过期。将 expireAfterSeconds 设为 0,由字段值直接决定过期时间。

db.sessions.createIndex(
  { "expireAt": 1 },
  { expireAfterSeconds: 0 }
)

修改 TTL 索引的过期时间

使用 collMod 命令修改已有 TTL 索引的 expireAfterSeconds 值,无需重建索引。

db.runCommand({
  "collMod": "log_events",
  "index": {
    "keyPattern": { "createdAt": 1 },
    "expireAfterSeconds": 600
  }
})

将普通索引转换为 TTL 索引(MongoDB 6.0+)

从 MongoDB 6.0 开始,可以通过 collMod 命令将已存在的普通单字段索引转换为 TTL 索引,而不需要先删除再重建。

db.runCommand({
  "collMod": "tickets",
  "index": {
    "keyPattern": { "lastModifiedDate": 1 },
    "expireAfterSeconds": 100
  }
})

监控 TTL 运行状态

您可以通过以下命令监控 TTL 的运行情况。

// 查看 TTL 删除的文档总数
db.serverStatus().metrics.ttl.deletedDocuments

// 查看 TTL 线程的运行轮次
db.serverStatus().metrics.ttl.passes

// 查看 TTL 线程的扫描周期(默认 60 秒)
db.runCommand({ getParameter: 1, ttlMonitorSleepSecs: 1 })

// 查看当前正在执行的 TTL 删除操作
db.currentOp()

TTL 索引的使用限制

在使用 TTL 索引前,需要了解以下重要限制:

  • TTL 索引仅支持单字段索引。复合索引无法使用 TTL 功能,即使设置了 expireAfterSeconds 也会被忽略。

  • _id 字段不支持 TTL 索引。

  • 索引字段必须为 BSON Date 类型。如果字段值不是 Date 类型(如 UNIX 时间戳整数、字符串等),文档将不会被自动删除。

  • 不包含索引字段的文档不会过期。

  • 如果字段是数组类型,使用数组中最早的日期值来计算过期时间。

  • 不能通过 createIndex 修改已有 TTL 索引,必须使用 collMod 命令。

  • expireAfterSeconds 的取值范围为 0 到 2,147,483,647,且不得设为 NaN,否则可能导致不可预期的行为和数据丢失。

常见问题与解决方案

问题一:TTL 删除导致 CPU 周期性飙高

问题现象

实例 CPU 使用率出现明显的周期性峰值,每隔固定时间间隔出现一次。通过 db.currentOp() 可以观察到 TTL 线程正在执行大量删除操作。

原因分析

当业务在同一时刻集中插入大量数据,并且这些数据的 TTL 字段值相同或接近时,它们会在同一时间窗口内集中过期。TTL 线程在单次扫描中需要删除大量文档,会导致显著的 CPU 和 IO 压力。

解决方案

  • 打散过期时间:在插入数据时,为 TTL 字段增加随机偏移量,将过期时间分散到一个时间窗口内,避免集中删除。例如,对于 24 小时过期的数据,可在过期时间上加减数分钟的随机偏移。

  • 升级到 MongoDB 7.0 及以上版本:利用批量删除和公平删除机制,平滑删除压力。

  • 选择合适的实例规格:确保实例的 CPU 和内存资源足以应对 TTL 删除带来的额外负载。

示例:为插入的数据添加随机过期偏移。

// 在 24 小时基础上增加 0~600 秒的随机偏移
var randomOffset = Math.floor(Math.random() * 600);
var expireTime = new Date(Date.now() + (86400 + randomOffset) * 1000);
db.logs.insertOne({
  data: "log content",
  createdAt: expireTime
})

问题二:数据过期后未被删除

问题现象

文档已超过预期的过期时间,但仍然存在于集合中。

排查步骤

  1. 确认字段类型:检查 TTL 索引字段的值是否为 BSON Date 类型。一个常见错误是使用 UNIX 时间戳整数而非 Date 对象。

    // 检查字段类型
    db.collection.findOne({}, { createdAt: 1 })
    // 正确示例:ISODate("2024-01-01T00:00:00Z")
    // 错误示例:1704067200 (整数时间戳)
  2. 确认字段是否存在:查看文档中是否实际包含了 TTL 索引字段。不包含该字段的文档不会被自动删除。

  3. 检查索引定义:确认索引确实包含 expireAfterSeconds 属性。

    db.collection.getIndexes()
  4. 检查索引方向:确保 TTL 索引是正序(值为 1)。倒序索引可能导致 TTL 功能异常。如发现索引方向不正确,删除后重建即可。

  5. 检查 TTL Monitor 是否开启:确认 ttlMonitorEnabled 参数为 true。

    db.adminCommand({ getParameter: 1, ttlMonitorEnabled: 1 })

问题三:TTL 删除速度跟不上数据插入速度

问题现象

集合数据量持续增长,磁盘空间不断膨胀,即使已配置 TTL 索引。

原因分析

TTL 线程是单线程且每 60 秒执行一次。当数据插入的速率远超过 TTL 清理速率时,过期数据会不断累积。此外,如果实例上存在多个 TTL 索引,它们之间是串行执行的,进一步降低清理效率。

解决方案

  • 调整扫描频率:可以通过 ttlMonitorSleepSecs 参数缩短扫描间隔,但需注意更高频率会增加系统负载。

    // 将扫描间隔调整为 10 秒
    db.adminCommand({ setParameter: 1, ttlMonitorSleepSecs: 10 })
  • 业务层面主动清理:对于数据写入量特别大的场景,建议在业务层实现定时批量删除逻辑,而不是完全依赖 TTL 索引。

  • 考虑使用时间分区集合:对于极高写入场景,可以按时间分集合存储,直接删除整个过期集合,这比 TTL 索引逐条删除文档高效得多。

问题四:删除后磁盘空间未释放

问题现象

TTL 删除了大量文档,但磁盘使用率并未显著下降。

解决方案

MongoDB 使用 WiredTiger 存储引擎,删除文档后空间会被标记为可重用,但不会立即返还给操作系统。如果需要立即回收磁盘空间,可以执行 compact 命令或通过初始化同步来重新构建数据文件。建议在业务低峰期进行 compact 操作。

最佳实践建议

一、确保数据类型正确

TTL 索引仅对 BSON Date 类型的字段生效。建议在应用层或通过 MongoDB 的 Schema Validation 功能强制确保 TTL 字段为正确的日期类型。

db.createCollection("sessions", {
  validator: {
    $jsonSchema: {
      properties: {
        expireAt: { bsonType: "date", description: "TTL 字段必须为 Date 类型" }
      },
      required: ["expireAt"]
    }
  }
})

二、分散过期时间,避免集中删除

这是避免 TTL 删除导致 CPU 飙高的最有效方法。在插入数据时,为过期时间添加随机偏移量,使同一批数据的过期时间分散到一个时间范围内。这样 TTL 线程每次扫描时只需要删除少量文档,显著降低对系统性能的影响。

说明

对于保留时间较长的数据(如 24 小时以上),建议在过期时间上添加 0 到 10 分钟的随机偏移。

三、谨慎调整 expireAfterSeconds

降低已有 TTL 索引的 expireAfterSeconds 值会导致大量已有文档立即过期,可能触发大规模删除操作,严重影响实例性能。建议的做法是:

  1. 先估算调整后会有多少文档立即过期。

  2. 在业务低峰期操作。

  3. 如果过期文档数量巨大,建议先通过业务脚本分批删除部分数据,再修改 expireAfterSeconds。

四、新建 TTL 索引时的注意事项

在一个已经包含大量符合过期条件的文档的集合上创建 TTL 索引,可能导致索引建立完成后立即触发大规模删除。建议:

  • 在业务低峰期创建 TTL 索引。

  • 先清理历史过期数据,再创建 TTL 索引。

  • 使用后台方式建索引,避免影响正常业务。

五、不要长期关闭 TTL Monitor

虽然可以通过 ttlMonitorEnabled 参数临时关闭 TTL 功能,但不建议在生产环境长期使用。关闭期间累积的大量过期文档会在重新开启时集中删除,可能导致严重的性能问题。

警告

临时关闭 TTL Monitor 后,务必及时重新开启,避免过期数据大量堆积。

六、合理评估 TTL 对 oplog 的影响

TTL 删除的每一条文档都会生成一条 oplog。当删除量较大时,会显著增加 oplog 的写入量,可能影响副本集的同步延迟。建议通过以下方式评估影响:

  • 监控 metrics.ttl.deletedDocuments,了解 TTL 删除量。

  • 监控副本集同步延迟,确保 Secondary 能处理 TTL 产生的额外 oplog。

  • 必要时调大 oplog 大小,以避免因 TTL 删除导致 oplog 溢出。

七、高写入场景的替代方案

TTL 索引适用于中等写入量的场景。对于极高写入量的场景,建议考虑以下替代方案:

  1. 时间分区集合:按时间维度拆分集合(如按天、按周),直接 drop 过期集合,这是最高效的数据过期方式。

  2. 时间序列集合:MongoDB 5.0+ 支持时间序列集合,内置数据过期功能,按桶批量删除,效率远高于普通集合的逐条删除。

  3. 业务层定时清理:通过定时任务在业务低峰期执行批量删除,可以更精细地控制删除速率和时机。

各版本 TTL 特性参考

版本

特性说明

6.0+

支持通过 collMod 将普通单字段索引转换为 TTL 索引,无需先删除再重建。

7.0+

引入批量删除机制(BATCHED_DELETE),提升删除效率;实现公平删除,避免某些集合被"饿死";支持时间序列集合的部分 TTL 索引,支持更灵活的 partialFilterExpression。

TTL 相关参数速查

参数名

说明

默认值

ttlMonitorSleepSecs

TTL 线程扫描间隔。

60 秒

ttlMonitorEnabled

TTL 功能开关。

true

ttlMonitorBatchDeletes

批量删除模式(7.0+)。

true

ttlIndexDeleteTargetTimeMS

每轮删除时间上限(7.0+)。

1000 ms

ttlIndexDeleteTargetDocs

每轮删除文档数上限(7.0+)。

50000

ttlMonitorSubPassTargetSecs

子轮遍历时间上限(7.0+)。

60 秒

总结

TTL 索引是 MongoDB 中一个强大且实用的自动数据过期机制,能够有效地管理具有明确生命周期的数据。在生产环境中,需要结合业务场景合理配置,以避免性能问题。以下是核心要点:

  1. 确保 TTL 字段类型为 BSON Date,这是 TTL 索引正常工作的前提。

  2. 打散过期时间是避免 CPU 毛刺的最有效手段。

  3. 持续监控 TTL 的运行状态(删除量、扫描轮次、副本集同步延迟)。

  4. 对于高写入场景,考虑使用时间分区集合或时间序列集合等替代方案。

  5. 升级到较新版本(如 7.0+)可获得更好的 TTL 删除性能和公平性。