ttl索引需同时满足expireafterseconds为正整数、索引方向为1、字段名与date类型值完全匹配,否则失效;后台每60秒扫描,副本集时间不同步会导致误删或延迟。

用 TTL 索引是最可靠、最省心的方式,MongoDB 后台线程会自动扫描并物理删除过期文档,不依赖应用层逻辑,也不怕服务重启漏删。
createIndex 里三个参数必须同时对齐
错一个,TTL 就完全不生效。常见失效场景全是这三个点没配对:
-
expireAfterSeconds必须是正整数(比如3600),不能是字符串"3600"、null或负数 - 索引方向只能是
1(升序),写成-1会被静默忽略,索引建了但 TTL 不触发 - 字段名要和文档里实际存的完全一致,比如文档存的是
expiredAt,但建索引用了expiresAt,那就彻底失效
expiresAt 字段必须是 Date 对象,不是时间戳也不是字符串
这是线上踩坑最多的地方。MongoDB 只认原生 Date 类型,其他格式哪怕看着像时间,TTL 也视而不见:
- ✅ 正确:
{ expiresAt: new Date(Date.now() + 3600 * 1000) }(Node.js) - ✅ 正确:
{"expiresAt": datetime.utcnow() + timedelta(hours=1)}(PyMongo) - ❌ 错误:
{"expiresAt": Date.now()}(number 类型) - ❌ 错误:
{"expiresAt": "2024-05-20T12:00:00Z"}(string 类型)
删除延迟和副本集时间不同步会影响判断
TTL 后台线程默认每 60 秒扫描一次,所以实际删除可能比设定时间晚最多 1 分钟。更隐蔽的问题出在副本集:
- 主节点写入
expiresAt,但从节点系统时间慢了几秒,就会提前删掉文档 - 用
db.runCommand({ serverStatus: 1 }).localTime检查各节点时间偏差 - 别在从节点上查
db.sessions.find()来判断 session 是否还存在——看到的可能是已删或未删的脏数据
真正麻烦的不是建索引,而是字段类型、索引方向、参数值三者咬合;只要有一个松动,TTL 就变成“假过期”——文档永远留在那里,磁盘只增不减。











