FOR UPDATE 必须在事务中生效,单独执行会报错或忽略;需BEGIN开启事务,确保WHERE命中索引以避免锁表,UPDATE本身也隐式加X锁。

FOR UPDATE 必须在事务中才生效
单独执行 SELECT ... FOR UPDATE 不会加锁,MySQL 会直接报错或静默忽略(取决于 SQL 模式),真正起作用的前提是当前连接已开启事务且未提交。常见错误是写完 SELECT ... FOR UPDATE 就立刻执行 UPDATE,但没用 BEGIN 或 START TRANSACTION 包裹,结果锁根本没拿到,后续更新发生并发覆盖。
实操建议:
- 务必显式开启事务:
BEGIN→SELECT ... FOR UPDATE→ 业务逻辑 →COMMIT或ROLLBACK - 避免在事务里做耗时操作(如远程调用、大循环),否则锁持有时间过长,阻塞其他事务
- 确认隔离级别:默认
REPEATABLE READ下,FOR UPDATE会对扫描到的索引记录加 next-key lock;若降为READ COMMITTED,则只加 record lock(不锁间隙)
行锁是否生效,取决于 WHERE 条件是否命中索引
FOR UPDATE 看似“锁行”,实际锁的是索引项。如果 WHERE 条件没走索引(例如对非索引字段查询、或用了函数/隐式类型转换),MySQL 会退化为锁全表(更准确地说:锁所有聚簇索引记录,即整张表的主键行),并发性能骤降。
验证方法:
- 执行前先看执行计划:
EXPLAIN SELECT ... FOR UPDATE,确认key列非NULL,且rows尽量小 - 检查是否因字符集/排序规则不一致导致索引失效,例如
WHERE name = 'abc',但name是utf8mb4_0900_as_cs而参数是utf8mb4_general_ci - 联合索引要注意最左匹配:若索引是
(a, b),WHERE b = 1无法使用该索引,自然也不触发行锁
阻塞不是 bug,而是锁等待超时触发的 ERROR 1205 或 ERROR 1213
两个事务同时尝试 FOR UPDATE 同一行,后到者不会立即失败,而是进入锁等待状态,默认等待 50 秒(由 innodb_lock_wait_timeout 控制),超时后抛出 ERROR 1205 (HY000): Deadlock found when trying to get lock(死锁)或 ERROR 1213 (40001): Deadlock found when trying to get lock(注意:1205 和 1213 实际都表示锁等待失败,具体报哪个取决于是否被检测为死锁)。
关键点:
- 死锁检测是自动的,但代价不低;高并发下频繁死锁说明业务逻辑存在竞争热点,比如多个服务争抢同一账户余额
- 不要依赖超时来“兜底”,应在应用层设计重试机制,并限制最大重试次数(避免雪崩)
- 用
SHOW ENGINE INNODB STATUS\G查看最近死锁详情,重点关注TRANSACTION和WAITING FOR THIS LOCK TO BE GRANTED部分
UPDATE 语句本身也会隐式加锁,不一定非要写 FOR UPDATE
很多人以为只有显式 SELECT ... FOR UPDATE 才加锁,其实普通 UPDATE 在执行时,MySQL 会对将要修改的行自动加 X 锁(排他锁),效果等同于先 SELECT ... FOR UPDATE 再 UPDATE。区别在于:前者可提前暴露锁冲突(在 SELECT 阶段就阻塞),后者把冲突推迟到 UPDATE 阶段。
适用场景选择:
- 需要基于当前值做判断再决定是否更新(如“余额 > 100 才扣款”),必须用
SELECT ... FOR UPDATE,否则可能读到旧值导致逻辑错误 - 只是简单 SET 字段(如
UPDATE t SET status = 'done' WHERE id = 123),直接UPDATE更简洁,也少一次网络往返 - 注意:
UPDATE若影响 0 行,锁也会释放;而SELECT ... FOR UPDATE即使查不到记录,也可能锁住间隙(gap lock),防止幻读










