MULTI/EXEC事务对缓存击穿完全无效,因其不提供原子锁语义,无法阻止多客户端并发查库;真正有效的是SET EX NX等原子写命令实现的“发现缺失→独占写→回填”链路。

Redis事务不能解决缓存击穿
直接说结论:MULTI/EXEC 事务机制对缓存击穿完全无效。它不提供原子性锁语义,也无法阻止多个客户端同时发现缓存缺失后并发查库。事务只是把一组命令打包发送、按序执行,但无法协调跨请求的“检查-重建”逻辑。如果你正在用 MULTI 包裹 GET + SET,那只是在做无意义的串行化——所有客户端仍会几乎同时进入“缓存未命中”分支。
为什么 setnx / set ex nx 才是关键
真正起作用的是 Redis 的原子写入命令,不是事务。缓存击穿防护依赖的是“**首次发现缺失 → 独占写权限 → 查库并回填**”这一链路的原子性保障:
-
SET key value EX 300 NX:只有 key 不存在时才设置,返回 1 表示抢到“重建权”,其他请求拿到 0 就该等待或重试 -
SETNX(已 deprecated)本质相同,但不支持过期时间,必须搭配EXPIRE,存在竞态风险 - 必须为锁 key 设置合理过期时间(如 5–10 秒),否则持有锁的进程崩溃会导致死锁
- 锁 key 和业务 key 应分离,例如业务 key 是
shop:123,锁 key 可设为lock:shop:123
实际代码中容易漏掉的三个细节
很多实现卡在“看似加了锁,实则没生效”的阶段,问题往往出在以下环节:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 没处理锁超时后的自动清理:只靠
EX不够,若业务逻辑执行超时,后续请求可能因锁残留而无限等待;建议用带唯一标识的锁值(如 UUID),配合 Lua 脚本校验后删除 - 等待策略写成忙等:
while(!exists(key)) { sleep(10) }会压垮线程池;应改用指数退避 + 最大重试次数,或直接返回旧缓存(如果允许逻辑过期) - 忽略空值缓存:即使加了锁,若数据库查不到数据,不写
NULL缓存,下次请求仍会触发锁竞争——这实际变成了缓存穿透问题
比事务更可靠的替代方案
如果你需要更强一致性或不想手写锁逻辑,优先考虑这些现成方案:
- 用 Redisson 的
RLock:自动续期、可重入、支持 wait-timeout,比原生命令更健壮 - 本地缓存 + Redis 组合(如 Caffeine + Redis):本地缓存有过期窗口,能拦截大部分重复请求,降低 Redis 层压力
- 逻辑过期方案:缓存值里嵌入时间戳,读取时判断是否需异步刷新,彻底避开同步阻塞
事务机制在 Redis 中只适合“多 key 批量更新且无需并发控制”的场景,比如用户积分+订单状态+库存扣减的原子写入。把它套在缓存重建流程里,等于用锤子拧螺丝——方向就错了。

















