MongoDB事务无法约束Redis操作,需应用层用分布式锁协调:先加锁,再执行MongoDB事务,最后操作Redis并释放锁;延时双删是防主从延迟的兜底措施。

MongoDB 多文档事务本身不跨系统,无法直接约束 Redis 操作;必须靠应用层协调。简单说:事务管 MongoDB 内部一致性,Redis 分布式锁管“谁有资格进这个事务 + 后续缓存操作”。
为什么不能只靠 MongoDB 事务
MongoDB 的多文档事务(包括分片集群上的分布式事务)只保证自身集合间原子性,对 Redis 完全无感知。即使你在事务里更新了 orders 和 inventory,也无法阻止另一个线程在事务提交后、Redis 更新前读到旧缓存。
- 事务提交成功 ≠ 缓存已刷新,中间存在时间窗口
- 事务回滚时,Redis 若已提前写入,就变成脏数据
- 事务不包含网络 IO,而 Redis 操作是网络调用,失败不可回滚
Redis 分布式锁该加在哪儿
锁的粒度和位置决定成败。不能锁太宽(拖慢吞吐),也不能锁太窄(漏掉关键路径)。推荐加在「业务逻辑入口 + 事务边界外」:
- 先用
SET key value NX PX 30000获取锁,key 建议含业务标识,如lock:order:12345 - 获取锁成功后,再开启 MongoDB 事务;锁必须在事务 开始前 获取,且在 所有 Redis 操作完成后 释放
- 不要在事务内执行
DEL或SETRedis 命令——网络超时会导致事务卡住甚至被中断 - 释放锁必须用 Lua 脚本校验 value,防止误删其他线程的锁
典型流程与易错点
以“扣库存并创建订单”为例,正确顺序是:加锁 → 查库存(MongoDB)→ 扣减并创建订单(MongoDB 事务)→ 删除缓存(Redis)→ 释放锁。常见翻车点:
- 缓存删除放在事务里:一旦
client.Del(ctx, "stock:1001")失败,整个事务不会回滚,但锁已释放,别人进来读到旧缓存 - 锁过期时间短于事务执行时间:导致锁自动释放,第二个请求闯入,出现超卖
- 没处理 Redis 连接异常:
redis.Nil错误被忽略,缓存没删干净,后续读请求命中旧值 - 用本地锁(如
sync.Mutex)替代 Redis 锁:在多实例部署下完全失效
延时双删是否还必要
加了分布式锁之后,延时双删不是必须,但仍是兜底手段。原因在于:锁只能防并发写,防不了 Redis 主从同步延迟或客户端读取从节点缓存的场景。
- 第一次删缓存:在事务前,清掉可能存在的旧值
- 第二次删缓存:在事务提交成功后,延迟 100–500ms 再删一次,覆盖主从复制间隙
- 两次删除都应带重试逻辑,例如用
backoff.Retry尝试 3 次,失败则发告警,不阻塞主流程
真正难的不是写对这几步,而是锁的生命周期管理——它横跨数据库事务和缓存操作,又依赖外部服务可用性。任何一环超时、丢包、配置错误,都会让一致性退化为“尽力而为”。生产环境建议把锁获取、事务执行、缓存操作全部打上 trace ID,并监控锁等待时长、事务提交率、缓存删除失败率这三个指标。


















