SELECT FOR UPDATE 只在显式事务中生效,需禁用自动提交并用 BEGIN 开启事务;锁范围取决于 WHERE 条件是否命中索引,无索引将导致全表锁;避免死锁须统一加锁顺序、禁用混合锁类型、减少事务内耗时操作;UPDATE 自带 X 锁,无需先 SELECT FOR UPDATE。

SELECT FOR UPDATE 只在事务中生效
单独执行 SELECT FOR UPDATE 不会加锁,必须显式开启事务(BEGIN 或 START TRANSACTION),且不能在自动提交模式下运行。否则语句看似执行成功,实则退化为普通查询,锁根本不会产生。
- 检查当前会话是否自动提交:
SELECT @@autocommit;,返回1表示开启,需先执行SET autocommit = 0; - 推荐写法:始终用
BEGIN; SELECT ... FOR UPDATE; [UPDATE/DELETE]; COMMIT;显式控制边界 - 如果用 ORM(如 SQLAlchemy、MyBatis),确认其事务配置未覆盖底层隔离级别或提前 commit
锁定范围取决于 WHERE 条件和索引
SELECT FOR UPDATE 加的是行级锁,但“哪些行被锁”完全由查询条件是否命中索引决定。没走索引时会升级为表锁(InnoDB 中实际是所有索引记录的 next-key 锁,效果接近全表扫描锁)。
- 主键或唯一索引查询(如
WHERE id = 100)→ 精确锁定单行 - 普通索引 + 范围查询(如
WHERE status = 'pending')→ 锁定所有匹配索引项及其间隙(next-key lock) - 无索引字段查询(如
WHERE remark LIKE '%test%')→ 全表扫描,锁住所有聚簇索引记录,高并发下极易阻塞 - 验证是否走索引:
EXPLAIN SELECT ... FOR UPDATE,重点看type(应为const/ref/range)和key字段
避免死锁的关键操作习惯
死锁不是异常,而是 InnoDB 的正常检测行为。多数源于多个事务以不同顺序访问相同资源。只要加锁顺序一致,就能大幅降低概率。
- 所有业务逻辑中,对多行加锁时,强制按主键升序排序后再查:
SELECT ... FOR UPDATE ORDER BY id ASC - 不要在事务内混合使用
SELECT FOR UPDATE和SELECT LOCK IN SHARE MODE - 避免在事务中执行耗时操作(如 HTTP 请求、文件读写),锁持有时间越长,冲突窗口越大
- 监控死锁日志:
SHOW ENGINE INNODB STATUS\G,重点关注LATEST DETECTED DEADLOCK段落
UPDATE 语句本身也隐式加锁,别重复 FOR UPDATE
如果目标只是“查出来再更新”,直接用 UPDATE ... WHERE 更高效。它内部已对匹配行加 X 锁,无需先 SELECT FOR UPDATE 再 UPDATE —— 多一次查询不仅浪费 I/O,还可能因中间状态变化导致业务逻辑错乱。
UPDATE orders SET status = 'shipped' WHERE id = 123 AND status = 'paid';
这种写法原子性强,还能防止并发修改覆盖(通过 status = 'paid' 做乐观校验)。只有需要基于查询结果做复杂判断(比如库存预占需先读取当前余量再决定是否扣减)时,才真正需要 SELECT FOR UPDATE。
最容易被忽略的是:锁只在事务提交或回滚后释放,哪怕你只执行了 SELECT FOR UPDATE 并没后续操作,连接不关闭、事务不结束,锁就一直挂着——这会悄无声息拖垮整个库的并发能力。










