MySQL并发更新靠锁机制与MVCC协同:UPDATE自动加行级X锁,主键/唯一索引精确锁行,普通索引触发间隙锁或临键锁,无索引则全表逐行加锁;防超卖须在WHERE中加入stock>0等业务校验;MVCC不参与写操作,仅提升读并发。

MySQL 并发更新问题,本质是多个事务同时修改同一行数据时如何保证一致性与正确性。核心不在“能不能并发”,而在于“怎么控制并发”——靠的是锁机制 + MVCC 协同工作,不是二选一,而是分工配合。
update 一定加锁,但加什么锁要看条件和索引
InnoDB 的 update 操作默认会加行级排他锁(X锁),且这个过程是自动的,不需要显式声明。关键点在于:
- 如果 WHERE 条件命中主键或唯一索引,只锁匹配的那1行(精确行锁);
- 如果 WHERE 条件走普通索引或没索引,可能触发间隙锁(Gap Lock)或临键锁(Next-Key Lock),锁住一个范围,防止幻读;
- 全表扫描更新(如没 WHERE 或 WHERE 无有效索引)会升级为表级锁(实际是逐行加锁+锁遍历路径),性能极差,应杜绝。
为什么“库存扣减”容易超卖?锁不是万能的
很多人误以为“update 加了锁就安全了”,但超卖仍会发生,典型原因:
- 只靠
UPDATE t SET stock = stock - 1 WHERE id = 2—— 锁住了行,但没校验业务逻辑(比如 stock 是否 > 0); - 线程 A 扣减后未提交,线程 B 虽被阻塞,但一旦 A 提交、B 立即执行,此时 stock 已为 0,B 还是会把 stock 变成 -1;
- 真正防超卖要靠条件前置校验:
UPDATE t SET stock = stock - 1 WHERE id = 2 AND stock > 0。这样 B 执行时找不到满足条件的行,影响行为 0,应用层可据此判断失败。
MVCC 不参与写操作,但它让读不拖慢写
MVCC 主要服务SELECT,解决的是“读-写冲突”。它对 update 的直接影响有限:
- update 本身不依赖 MVCC 版本读,它总是读取最新已提交版本(或当前事务修改中的版本),然后加锁修改;
- 但 MVCC 提供的快照能力,让其他事务的 SELECT(在 RC/RR 隔离级别下)可以不被 update 阻塞,从而提升整体并发吞吐;
- 真正协调“多写并发”的,还是锁:InnoDB 用锁等待 + 死锁检测来序列化写操作,MVCC 是它的“读友好搭档”,不是替代者。
实战建议:从 SQL 写法到事务设计
应对并发更新,不能只靠数据库,需端到端设计:
-
SQL 层:WHERE 必带索引字段 + 业务约束(如
stock > 0、version = ?),避免无条件 update; -
应用层:对 update 返回的
affected rows做判断,为 0 就重试或报错,别假设一定成功; - 事务粒度:尽量缩短事务生命周期,避免长事务持锁;高并发场景可考虑用乐观锁(version 字段)减少锁竞争;
-
监控兜底:开启
innodb_print_all_deadlocks,定期查SHOW ENGINE INNODB STATUS,及时发现锁争用热点。










