应前置redis锁做准入过滤,mysql行锁仅用于最终一致性校验与原子更新;二者须解耦且语义对齐,避免锁粒度错配、超时绕过及主从延迟导致的并发问题。

直接用 MySQL 行锁扛高并发写请求,很容易在 SELECT ... FOR UPDATE 上卡住、锁表、连接池打满——这不是锁没用,是锁用得太重了。真正该做的,是把「是否需要走数据库」这个判断前置到 Redis 层,让绝大多数请求连 DB 都不碰。
为什么不能直接在 MySQL 里“加个 Redis 锁”就完事
常见误区是:业务代码里先调 SETNX 加 Redis 锁,再进事务执行 FOR UPDATE。这看似两层防护,实则埋雷:
- Redis 锁和 MySQL 行锁之间没有因果约束——Redis 锁释放了,MySQL 事务可能还在跑;MySQL 事务回滚了,Redis 锁可能还挂着
- 如果 Redis 锁超时而业务未完成,后续请求可能绕过锁直接进 DB,触发行锁竞争甚至死锁
- 锁 key 设计若没对齐业务维度(比如用用户 ID 锁库存扣减),会导致锁粒度错配,该串行的没串,不该等的干等
正确的分层策略:Redis 锁做“准入过滤”,MySQL 行锁只守“最终落地”
核心逻辑是:让 Redis 分布式锁承担「业务入口排他」职责,MySQL 只负责「数据一致性校验+原子更新」,两者解耦但语义对齐。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
以秒杀扣库存为例:
- Redis 锁 key 用
"seckill:sku:{skuId}",value 存唯一请求标识(如 traceId + 线程ID) - 加锁命令必须原子:用
SET key value EX 10 NX,10 秒是预估最长业务耗时,不是随意填的 - 加锁失败直接返回“稍后再试”,不进 MySQL;加锁成功才查缓存/DB,做库存预判
- 真正扣减时,MySQL 仍要用
UPDATE stock SET available = available - 1 WHERE sku_id = ? AND available > 0—— 这条 SQL 自带乐观校验,失败就说明库存不足,此时立刻删 Redis 锁并 return
必须处理的三个衔接断点
Redis 和 MySQL 之间的缝隙,恰恰是并发问题高发区:
-
锁续期时机:业务执行中若接近 10 秒还没结束,需用守护线程调
GETSET或 Lua 脚本刷新 TTL,但前提是 value 仍匹配当前线程标识,否则不续 -
解锁双保险:业务完成后,先删 Redis 锁(用 Lua 脚本比对 value 再
DEL),再 commit MySQL 事务;若事务 rollback,也要确保 Redis 锁被清理(可用 finally 块或补偿任务) -
主从延迟兜底:Redis 锁本身无主从强一致,所以 MySQL 的
WHERE available > 0不可省——它才是最终防线,Redis 锁只是减少无效请求的“漏斗”
最关键的细节往往藏在衔接处:Redis 锁的 value 必须全局唯一且可追溯,MySQL 的 WHERE 条件必须包含业务有效性判断,两者缺一不可。漏掉任意一环,所谓的“减轻压力”就会变成“转移压力”。










