MySQL悲观锁需在事务中用SELECT...FOR UPDATE或LOCK IN SHARE MODE显式加锁,配合REPEATABLE READ隔离级;锁随事务结束释放,须避免无索引查询、锁范围过大及死锁。

在 MySQL 中实现悲观锁控制并发更新,核心是借助数据库的行级锁机制,在事务中显式加锁,确保同一时间只有一个事务能修改某条记录。关键在于合理使用 SELECT ... FOR UPDATE 或 SELECT ... LOCK IN SHARE MODE,并配合事务隔离级别(推荐 REPEATABLE READ)和正确提交/回滚逻辑。
明确加锁场景与语句写法
悲观锁不是自动生效的,必须在事务内主动发起带锁查询:
-
排他锁(FOR UPDATE):适用于“读取后要更新”的典型场景,如扣减库存、转账。它会锁定查到的行(含间隙),阻止其他事务对该行做
UPDATE、DELETE或再次FOR UPDATE。 - 共享锁(LOCK IN SHARE MODE):适用于需校验但不立即修改的场景,如检查余额是否充足后再决定是否下单。其他事务仍可加共享锁,但不能加排他锁。
- 注意:
FOR UPDATE在REPEATABLE READ下默认使用 next-key lock(行锁 + 间隙锁),能防止幻读;若只查主键且值存在,则退化为行锁。
保证事务完整性与锁生命周期
锁只在事务内有效,事务结束(COMMIT 或 ROLLBACK)时自动释放:
- 务必开启显式事务(
BEGIN或START TRANSACTION),避免语句自动提交导致锁瞬间释放。 - 加锁查询与后续
UPDATE必须在同一个事务中,否则锁已失效。 - 避免长事务:锁持有时间越长,并发阻塞越严重。业务逻辑应尽量精简,不要在事务中做耗时操作(如远程调用、文件读写)。
规避常见陷阱
很多并发问题源于对锁行为理解偏差:
-
没走索引 = 表锁:如果
FOR UPDATE的WHERE条件未命中索引,MySQL 可能升级为表级锁,极大降低并发能力。务必确认执行计划(EXPLAIN)显示走了索引。 -
锁范围超出预期:例如
SELECT * FROM t WHERE id > 100 FOR UPDATE会锁住所有满足条件的行及之间的间隙,可能影响无关记录的插入。 -
死锁风险:多个事务以不同顺序访问多行时易触发死锁。MySQL 会自动检测并回滚其中一个事务(报错
Deadlock found),应用层需捕获并重试。
简单示例:安全扣减库存
假设商品表 products(id, stock),扣减 ID=123 的库存 1 件:
START TRANSACTION; SELECT stock FROM products WHERE id = 123 FOR UPDATE; -- 应用层判断 stock >= 1 UPDATE products SET stock = stock - 1 WHERE id = 123; COMMIT;
若库存不足,应在事务内直接 ROLLBACK 并返回错误,避免无效更新。










