DEBUG.WIKI
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国

MongoDB TTL 索引不删除文档:epoch 数字不是 BSON Date

MongoDB TTL 不删除文档时,先核对索引字段与 BSON 类型。转换数字日期前确认秒或毫秒、备份过期数据,并检查后台删除与节点角色。

集合已经给 updated_at 创建 TTL 索引,文档看起来早已过期,MongoDB 却一直不删除。先查看字段在数据库里的 BSON 类型:Unix 时间戳即使表示正确的时间,存成 NumberLong、普通数字或字符串,也不会被普通 TTL 索引当作日期处理。

本文针对普通集合的单字段 TTL 索引。时序集合的过期设置另有规则,不能直接套用这些检查。字段类型不符时,调整 TTL 扫描间隔或重启服务不会把数字变成 BSON Date。

先把索引字段和实际数据对上

在正确的数据库、集合中检查索引定义及几条目标文档。以下是结构示例,events、updated_at 需替换成自己的集合和字段;查询结果可能包含业务数据,应在受控环境查看:

db.events.getIndexes()
db.events.findOne({}, { updated_at: 1 })

索引应指向实际存储的字段,而不是应用模型的别名。普通 TTL 索引只能使用一个字段;复合索引上的 expireAfterSeconds 不会使它成为有效的 TTL 索引,_id 上也不能创建 TTL 索引。

下面两种结构的时间含义可以接近,但 BSON 类型不同。此处只是示例文档:

{ updated_at: NumberLong("1589964263") }
{ updated_at: ISODate("2020-05-20T09:00:00Z") }

第一种是数字,TTL 不会据此删除文档。第二种是日期,才可以与 expireAfterSeconds 一起计算过期时间。字段缺失,或值中没有可用日期,也不会使文档过期。若字段是日期数组,MongoDB 使用其中最早的日期,不能把数组里的最新值当成保留期限。

同时核对自己的保留规则。expireAfterSeconds 表示相对于索引日期的秒数,并不检查另一个数字字段是否已经超过业务设定的时间。

转换时先确认时间戳单位

数字时间戳可能以秒或毫秒计。MongoDB 将数值转换成 Date 时按距 Unix epoch 的毫秒解释;把秒数直接转换会得到接近 1970 年的日期,可能让数据立刻进入过期范围。

下面是只投影转换结果的聚合示例,不写回文档。它只选择 BSON long 类型,并假定这些 long 已确认为 epoch 秒;不能用于单位混杂的集合:

db.events.aggregate([
  { $project: { updated_at: 1, storedType: { $type: "$updated_at" } } },
  { $match: { storedType: "long" } },
  { $limit: 5 },
  { $project: {
      updated_at: 1,
      datePreview: {
        $convert: {
          input: { $multiply: ["$updated_at", 1000] },
          to: "date",
          onError: null,
          onNull: null
        }
      }
  } }
])

datePreview 为 null 时,不能继续当作有效日期迁移。检查超范围值、缺失值及原数据含义;这个小样本也不能证明整个集合都采用相同单位。已是毫秒的数字不应再乘以 1000,已是 Date 的字段也无需按数字重新转换。

应用的新写入同样要修正:通过实际驱动写入 BSON 日期类型,而不是只让 API 返回格式看起来像日期。字符串中的 ISO 时间文本也不等于数据库中的 BSON Date。检查写入后的真实文档,再判断模型修复是否生效。

把旧字段改成日期,可能立即触发删除

如果 TTL 索引已经存在,把旧数字改成日期会让符合过期条件的文档开始参与清理。这是改变数据保留行为,不能把它当成没有影响的格式整理。

迁移前备份受影响的数据、索引定义与应用写入配置,并核对恢复方法。先在隔离副本计算哪些文档转换后将过期,确认这些数据确实允许删除,再安排正式迁移。需要保留的历史数据应先按业务规则另行归档,不应依靠 TTL 扫描“可能还没运行”争取时间。

若计划临时移除并重建 TTL 索引,应单独评估索引构建与清理恢复后的负载,并记录原定义。更改或缩短 expireAfterSeconds 也可能使大量文档同时过期。具体数据迁移与索引调整应使用已审核的发布流程,不在生产集合上试验猜测的单位。

类型正确以后,仍要区分等待和失效

TTL 删除由后台任务执行,过期后不保证立即消失。当前 MongoDB 的后台任务通常每 60 秒检查,工作负载和待删除数量还可能造成更长延迟。这不是“等待一分钟一定删除”的承诺。

副本集中由 primary 执行 TTL 删除,secondary 接收复制结果。查看 secondary 时还要考虑复制延迟,不能因为没有在该节点看到独立删除任务,就认定索引无效。

验收时确认索引字段和文档类型正确、新写入确实使用 Date,随后观察允许过期的文档被删除、未过期文档保留。若仍无删除,再结合节点角色、负载与 TTL 统计继续检查。

恢复原应用或索引配置不会自动找回已经删除的文档。发生错误清理时,应根据保存的备份与业务恢复方案处理数据,同时阻止仍在生成错误日期的新写入。