select for update 必须在显式事务中执行,否则锁立即释放;需确保where条件走主键或唯一索引,避免表锁或间隙锁;多行操作须按统一顺序加锁防死锁;应根据读写需求选择for update或lock in share mode。

SELECT FOR UPDATE 必须在显式事务里执行
单独执行 SELECT * FROM order WHERE id = 123 FOR UPDATE 没用——如果 autocommit=1(MySQL 默认),这条语句一结束锁就释放,后续 UPDATE 完全不受保护。必须用 BEGIN 或 START TRANSACTION 包住整段逻辑。
常见错误:JDBC 中没调用 connection.setAutoCommit(false);Spring Boot 里没加 @Transactional;MyBatis XML 中写了 FOR UPDATE 却没配 transactionManager。
- 事务必须手动开启,不能依赖框架自动代理失效的场景(比如非 public 方法、异步线程)
- 事务范围要窄:只包
SELECT FOR UPDATE+ 数据校验 +UPDATE,别塞日志、HTTP 调用、循环查其他表 - 超时要设:MySQL 默认
innodb_lock_wait_timeout=50秒,业务应捕获Lock wait timeout exceeded并快速失败或降级
WHERE 条件必须走主键或唯一索引
SELECT ... FOR UPDATE 看似锁一行,实际锁什么,全看执行计划。用 EXPLAIN 确认是否命中索引——否则 InnoDB 会升级为表锁或间隙锁,整个库可能卡住。
典型翻车现场:SELECT * FROM product WHERE sku = 'A001' FOR UPDATE,但 sku 字段没建索引,结果锁了整张表;又或者 WHERE status = 'pending' 这种低区分度字段,锁住成百上千行。
- 主键查询最稳:
WHERE id = ? - 联合唯一索引也行:
WHERE order_id = ? AND version = ?(前提是该组合有唯一索引) - 避免用
LIKE '%xxx'、函数包装字段(如WHERE DATE(created_at) = '2026-07-21')、OR多条件混合
排他锁(FOR UPDATE)和共享锁(LOCK IN SHARE MODE)别混用
要不要允许别人同时读?这决定用哪个锁。共享锁不阻塞读,但阻塞写;排他锁连快照读(SELECT)在 READ COMMITTED 以下隔离级别都会被阻塞。
例如审核流程:SELECT status FROM order WHERE id = 123 LOCK IN SHARE MODE → 校验权限 → UPDATE order SET status = 'approved'。这里中间不能穿插别的写操作,否则共享锁一释放,别人就可能抢先改掉状态。
- 后续有
UPDATE或DELETE,必须用FOR UPDATE - 纯读+需防止写(比如生成报表时锁定统计基准),可用
LOCK IN SHARE MODE - MySQL 8.0+ 推荐用
FOR SHARE替代LOCK IN SHARE MODE,语义更清晰
多行更新必须统一锁顺序防死锁
两个事务分别按不同顺序锁多行,是死锁高发区。比如转账:事务 A 先锁账户 1001 再锁 1002,事务 B 反过来,双方卡住等对方释放。
解决办法不是靠重试,而是从源头约束顺序。最简单可靠的是按主键升序加锁:
SELECT * FROM account WHERE id IN (1002, 1001) ORDER BY id FOR UPDATE;
这样无论传参顺序如何,最终都先锁 1001 再锁 1002。
- 批量扣库存时,
WHERE sku IN ('A','B','C')后加ORDER BY sku - 不要依赖应用层排序后拼 SQL,InnoDB 不保证解析顺序就是执行顺序
- 死锁日志里出现
*** WE WILL ROLL BACK TRANSACTION (1)就是这个原因,不是性能问题,是逻辑缺陷
锁生效与否,不取决于你写了 FOR UPDATE,而取决于事务是否开启、索引是否命中、锁顺序是否一致。这三个点漏掉任何一个,线上都可能超卖或死锁。










