
本文介绍如何在Go中使用mgo驱动为MongoDB集合创建TTL索引,使带有效expireAt字段的文档自动过期删除,而缺失该字段的文档永久保留,从而实现同一集合内“持久化”与“临时性”文档的混合管理。
本文介绍如何在go中使用mgo驱动为mongodb集合创建ttl索引,使带有效`expireat`字段的文档自动过期删除,而缺失该字段的文档永久保留,从而实现同一集合内“持久化”与“临时性”文档的混合管理。
在实际业务场景中(如日志缓存、分享链接、临时凭证等),常需在同一MongoDB集合中同时存储两类文档:一类需长期保留(如核心配置),另一类则需在指定时间后自动清理(如14天后失效的共享记录)。MongoDB原生支持的TTL(Time-To-Live)索引正是为此设计——但其行为高度依赖字段值的有效性与存在性,而非简单的时间比较。
关键在于:MongoDB TTL索引仅对包含有效BSON日期值的expireAt字段生效;若该字段完全不存在(即未写入),或显式设为null,则文档不会被TTL机制处理。这与time.Time{}零值(即0001-01-01T00:00:00Z)有本质区别——后者是合法的UTC时间,在MongoDB中会被视为一个极早的过期时间,导致文档几乎立即被删除,绝不可用于表示“不过期”。
因此,正确做法是利用mgo的omitempty标签控制字段序列化行为,确保“永不过期”的文档在插入时不携带expireAt字段:
type Filter struct {
Timestamp time.Time `bson:"createdAt"`
Body string `bson:"body"`
ExpireAt time.Time `bson:"expireAt,omitempty"` // ← 关键:零值不写入
}配合此结构体,创建TTL索引时需将ExpireAfter设为0(对应MongoDB的expireAfterSeconds: 0),表示直接以expireAt字段值作为精确过期时间点:
立即学习“go语言免费学习笔记(深入)”;
index := mgo.Index{
Key: []string{"expireAt"},
ExpireAfter: 0, // ← 必须为0,才能启用"基于字段值的精确过期"
}
err := collection.EnsureIndex(index)
if err != nil {
log.Fatal("Failed to create TTL index:", err)
}此时,插入逻辑可清晰分离两种语义:
// 情况1:创建永久文档(不设置ExpireAt → 字段被忽略)
permanent := Filter{
Timestamp: time.Now(),
Body: "This stays forever",
// ExpireAt 未赋值 → bson.Marshal后不包含该字段
}
collection.Insert(permanent)
// 情况2:创建14天后过期的文档(显式设置有效时间)
volatile := Filter{
Timestamp: time.Now(),
Body: "Expires in 14 days",
ExpireAt: time.Now().Add(14 * 24 * time.Hour), // ← 合法未来时间
}
collection.Insert(volatile)✅ 注意事项:
- ExpireAfter: 0 是启用“字段值驱动TTL”的唯一方式;若设为正数(如120),MongoDB会将expireAt视为基准时间,并统一延后120秒过期,失去灵活性。
- 零值time.Time{}必须避免——它会被序列化为ISODate("0001-01-01T00:00:00Z"),触发立即删除。omitempty是安全屏障。
- TTL后台任务每60秒扫描一次,过期文档的实际删除存在分钟级延迟,不可用于强实时场景。
- 若需动态变更文档过期策略,可用Update()添加/修改expireAt字段,MongoDB会自动纳入TTL管理。
综上,通过omitempty控制字段存在性 + ExpireAfter: 0索引配置,即可在单一集合中优雅实现“按需过期”语义,兼顾数据模型简洁性与运维可控性。


















