MongoDB 7.0不支持在聚合管道中触发TTL删除,TTL过期由后台线程独立执行;聚合管道仅能通过$match配合已建的TTL索引字段(如{expireAt: 1})高效识别待处理数据,无法干预删除机制本身。

直接说结论:MongoDB 7.0 **不支持在聚合管道中触发TTL删除**,TTL过期由后台线程独立执行,聚合管道只能「发现」或「辅助归档」即将/已经过期的数据,不能替代或干预TTL机制本身。
$match 阶段必须用 _id 或带 TTL 索引的字段过滤
TTL索引(如 { "expireAt": 1 })是唯一能触发自动删除的机制。聚合管道若想高效识别待归档数据,$match 必须作用于该索引字段,否则无法利用索引加速——哪怕写成 { expireAt: { $lt: new Date() } },若 expireAt 没建 TTL 索引,就是全集合扫描。
- ✅ 正确做法:先确保已创建 TTL 索引
db.collection.createIndex({ expireAt: 1 }, { expireAfterSeconds: 0 }) - ❌ 错误写法:
$match: { createdAt: { $lt: ISODate("...") } }(除非createdAt也建了普通索引且选择性高) - ⚠️ 注意:
expireAfterSeconds: 0表示“到期即删”,不是延时;设为正数会延迟删除,但归档逻辑需同步调整时间偏移
归档动作必须用 $merge 或应用层搬运,不能靠 $out
TTL 不提供“移动到归档集合”的能力,只能删除。归档需主动导出数据,而聚合管道里 $out 会**完全替换目标集合**,风险极高;$merge 才是安全选择——它支持 upsert、保留历史、避免覆盖。
- ✅ 推荐归档管道片段:
db.source.aggregate([ { $match: { expireAt: { $lt: new Date() } } }, { $merge: { into: "archive_collection", on: "_id", whenMatched: "replace", whenNotMatched: "insert" } } ]) - ❌ 禁止用
$out做归档:{ $out: "archive_collection" }会清空已有归档数据,不可逆 - ⚠️ 关键限制:视图(view)定义中禁止使用
$merge或$out,所以归档逻辑不能封装进视图
避免在聚合中调用 $expr 计算过期时间
有人试图用 $expr 动态算“30天前”,比如 { $expr: { $lt: ["$createdAt", { $subtract: [ "$$NOW", 2592000000 ] }] } }。这会导致索引失效——即使 createdAt 有索引,$expr 内部计算也无法命中。
- ✅ 安全替代:在应用层生成固定时间点
threshold = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),传入聚合作为字面量 - ❌ 不要依赖
$$NOW或$dateSubtract在$match中做条件,除非你确认该字段有对应索引且查询计划显示 `IXSCAN` - ? 小技巧:用
explain("executionStats")验证是否走索引:db.collection.aggregate([...]).explain("executionStats")查看nReturned和totalDocsExamined是否接近
真正容易被忽略的是 TTL 索引和聚合管道的职责边界:TTL 只管删,不管存;归档逻辑一旦耦合进定时任务,就必须处理并发冲突(比如同一条文档被两个归档任务同时读取)、幂等写入($merge 的 whenMatched 策略选错会导致重复计数),以及归档后原集合文档是否真被 TTL 清理掉——这取决于后台线程执行周期,不是立即发生。


















