
本文详解如何在 Go(基于 mgo 驱动)中正确创建 MongoDB 的 TTL(Time-To-Live)索引,重点说明为何 EnsureIndex 不会覆盖已有索引、必须显式删除旧索引或集合才能生效,并提供可运行的完整示例与关键注意事项。
本文详解如何在 go(基于 mgo 驱动)中正确创建 mongodb 的 ttl(time-to-live)索引,重点说明为何 `ensureindex` 不会覆盖已有索引、必须显式删除旧索引或集合才能生效,并提供可运行的完整示例与关键注意事项。
MongoDB 的 TTL 索引是一种特殊单字段索引,允许数据库自动删除过期文档(例如 session、缓存、日志等)。在 Go 中使用 mgo 驱动创建 TTL 索引看似简单,但一个常见且隐蔽的陷阱是:EnsureIndex 不会更新已存在的同名索引——它仅在索引不存在时创建,若索引已存在(即使结构不同),则直接跳过,导致 ExpireAfter 设置被静默忽略。
你提供的代码逻辑本身正确:
sessionTTL := mgo.Index{
Key: []string{"created"},
Background: true,
ExpireAfter: 5 * time.Second, // 注意:单位是秒(非纳秒),且必须为 int64 秒数
}
err := db.C("session").EnsureIndex(sessionTTL)
但问题根源在于:"session" 集合中已存在名为 { created: 1 } 的普通索引(例如之前未设 TTL 时创建的),EnsureIndex 检测到该索引键已存在,便不再应用新的 ExpireAfter 配置。MongoDB 要求 TTL 索引必须是唯一的新建索引,无法通过“升级”方式添加过期行为。
✅ 正确做法分两步:
-
显式删除旧索引(推荐,保留数据):
// 删除所有以 "created" 开头的索引(安全起见建议先 list 查看) err := db.C("session").DropIndex("created") if err != nil { log.Fatal("Failed to drop old index:", err) } // 再确保创建带 TTL 的新索引 err = db.C("session").EnsureIndex(sessionTTL) if err != nil { log.Fatal("Failed to create TTL index:", err) } -
或清空集合重建(开发/测试环境快捷方式):
err := db.C("session").DropCollection() // ⚠️ 删除整个集合!慎用于生产 if err != nil { log.Fatal(err) } err = db.C("session").EnsureIndex(sessionTTL)
? 关键注意事项:
-
ExpireAfter 的单位是秒(int64),不是 time.Duration 的纳秒值。5 * time.Second 在 Go 中是 5e9 纳秒,但 mgo.Index.ExpireAfter 期望的是秒数整型(如 5)。错误写法会导致索引创建失败或行为异常。正确写法应为:
ExpireAfter: int64(5), // 5 秒后过期
或显式转换:ExpireAfter: int64(session_expire.Seconds())(需确保 session_expire 是 time.Duration)。
-
字段 "created" 必须是 UTC 时间戳(ISODate 类型),且不能是嵌套字段或数组。建议插入时统一使用:
doc := bson.M{ "created": time.Now().UTC(), "data": "some-session-data", } TTL 删除由 MongoDB 后台线程每 60 秒扫描一次触发,因此实际删除可能延迟最多 60 秒(不可用于精确时效控制)。
-
mgo 已归档,生产项目建议迁移到官方驱动 mongo-go-driver,其 TTL 创建更明确:
model := mongo.IndexModel{ Keys: bson.D{{"created", 1}}, Options: options.Index().SetExpireAfterSeconds(5), } _, _ = collection.Indexes().CreateOne(ctx, model)
总结:TTL 索引不是“属性补丁”,而是索引类型声明。EnsureIndex 的幂等性设计是双刃剑——保障并发安全,但也要求开发者主动管理索引生命周期。务必验证索引是否真正生效:连接 MongoDB Shell 执行 db.session.getIndexes(),确认输出中存在 "expireAfterSeconds": 5 字段,而非仅有 "key": { "created": 1 }。











