ttl索引是清理session最省心的方式,因mongodb自动删除过期文档,无需定时任务、不依赖应用心跳、不因重启漏删;需确保expiresat为date类型、单字段升序索引、expireafterseconds为正整数。

为什么 TTL索引 是清理 session 最省心的方式
因为 MongoDB 会自动删除过期文档,不需要你写定时任务、不依赖应用层心跳、也不会因服务重启漏删。它靠后台线程扫描 _id 或指定时间字段,触发物理删除——前提是字段类型是 Date,且索引建对了。
常见错误现象:db.sessions.find({ expiresAt: { $lt: new Date() } }) 能查出旧数据,但文档就是不消失;或者插入后几秒就没了,比设置的 expireAfterSeconds 短得多。
-
expiresAt字段必须是Date类型,不能是字符串或数字时间戳(哪怕格式正确) - 索引必须建在单个字段上,不能是复合索引的一部分(
{ expiresAt: 1, userId: 1 }无效) - 后台线程默认每 60 秒检查一次,所以实际删除可能延迟 1 分钟左右
createIndex 命令里最容易写错的三个参数
不是所有字段都能挂 TTL,也不是所有写法都生效。关键就看这三个点是否对齐:
-
expireAfterSeconds必须是正整数(比如3600),不能是"3600"字符串或null - 索引方向只能是
1(升序),写成-1会静默忽略 TTL 行为 - 字段名要和你写入时用的完全一致,比如存的是
expiredAt,但建索引用了expiresAt,那就彻底失效
正确示例:
db.sessions.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 3600 })
Session 文档写入时必须带 Date 字段,不是“加个时间戳”就行
很多同学用 new Date().getTime() 存成毫秒数,或者拼个 "2024-05-20T12:00:00Z" 字符串,结果 TTL 完全不触发——MongoDB 只认原生 Date 对象。
- Node.js 写法:
{ _id: "...", expiresAt: new Date(Date.now() + 3600 * 1000) } - Python PyMongo:
{"expiresAt": datetime.utcnow() + timedelta(hours=1)} - 绝对不要:
{"expiresAt": Date.now()}(这是 number)、{"expiresAt": "2024-05-20T12:00:00Z"}(这是 string)
超时时间不准?先确认你没在读写分离架构里踩坑
如果用副本集,主节点写入 expiresAt,但从节点时间不同步(差几秒甚至几十秒),就会导致从节点提前/延后触发删除——这在跨时区部署或 NTP 未校准的机器上很常见。
- 用
db.runCommand({ serverStatus: 1 }).localTime检查各节点时间偏差 - 避免在从节点上直接查询
session是否还存在来判断登录状态(可能看到已删或未删的脏数据) - 生产环境务必开启 NTP 同步,且
ntpq -p显示 offset
真正麻烦的不是建索引,而是时间字段的类型、值、集群时钟三者咬合不上。只要这三个点对齐了,TTL索引 就几乎零维护。










