UPDATE同一行卡住服务是因为InnoDB行锁在热点场景下成为串行瓶颈;需通过拆分表、分桶设计、Redis预扣减+异步落库等“拆”和“绕”策略缓解锁争用。

为什么 UPDATE 同一行会卡住整个服务?
不是锁不够细,而是所有请求在抢同一把“门禁卡”——InnoDB 的行锁在热点场景下反而成了串行瓶颈。比如 UPDATE goods SET stock = stock - 1 WHERE id = 123,哪怕 QPS 只有 2000,只要集中在同一 id,事务就得排队等前一个提交释放 X 锁。更糟的是,如果事务里还夹着发消息、查用户信息、调外部 API,那这把锁可能被攥着上百毫秒,等待队列指数膨胀,CPU 直奔 95%。
怎么让 MySQL 别老盯着那一行?
核心思路是“拆”和“绕”:把单点压力打散,或把写操作从数据库里挪出去。实操上优先落地成本低、见效快的几条:
- 用
INSERT INTO ... ON DUPLICATE KEY UPDATE替代先SELECT再UPDATE,避免额外加读锁 + 持锁时间翻倍 - 高频更新字段(如库存、积分)必须拆到独立子表,例如
goods_stock单独建,别和goods主表挤在一起 - 对确定的热点行(如秒杀商品),直接改用分桶设计:
goods_id+shard_id联合主键,10 个分片就摊薄 90% 锁冲突 - 事务内只做 DB 操作,删掉日志打印、远程调用、非必要
SELECT FOR UPDATE;UPDATE尽量放到事务最后执行,缩短锁持有时间
Redis 预扣减 + 异步落库,真能不用锁?
能,但得接受短暂不一致。这不是妥协,而是权衡:用户看到“已抢到”,数据库还没写完,中间差个几十毫秒,对秒杀这种场景完全可接受。关键要稳住两头:
在原版的基础上做了一下修正评论没有提交正文的问题特价商品的调用连接问题去掉了一个后门补了SQL注入补了一个过滤漏洞浮动价不能删除的问题不能够搜索问题收藏时放入购物车时出错点放入购物车弹出2个窗口修正主题添加问题商家注册页导航连接问题销售排行不能显示更多问题热点商品不能显示更多问题增加了服务器探测 增加了空间使用查看 增加了在线文件编辑增加了后台管理里两处全选功能更新说明:后台的部分功能已经改过前台
- 用 Redis Lua 脚本做原子预扣:
DECR成功才放行,失败直接返回,绝不穿透到 MySQL - 异步写 DB 用消息队列(如 RocketMQ),消费端控制速率(如每秒 300 条),避免反压打垮数据库
- 加幂等保障:消息体带唯一业务 ID,DB 层用
INSERT IGNORE或ON DUPLICATE KEY UPDATE防重
哪些“优化”反而会让锁问题更隐蔽?
最容易踩坑的是自以为在优化,实则放大了争用:
- ORM 自动生成的
UPDATE没走索引,导致行锁升级成间隙锁,一把锁锁住一整段索引范围 - 连接池
maximumPoolSize设成 100,结果瞬间涌进 800 个请求,锁队列雪崩式堆积,应用线程全卡在getConnection() - 缓存没设过期或没做穿透保护,缓存失效瞬间,所有请求批量击穿到 DB,制造突发热点
- 长期开着
REPEATABLE READ隔离级别却没意识到它会多持锁(尤其 Next-Key Lock),而业务其实只需要READ COMMITTED
真正治本的从来不是调参或换数据库,而是把“必须强一致”的假设打碎——先问一句:这里真的不能等 100ms 吗?









