select for update默认加行级排他锁(x锁),但实际锁范围取决于索引命中情况、where条件精度和隔离级别;未走索引则退化为表锁,且必须显式开启事务(autocommit=0)才生效。

它加的是行级排他锁(X锁),但实际锁住的范围不一定是“一行”——取决于索引、WHERE 条件和隔离级别。没走索引或条件不精确,可能直接升级为表锁。
SELECT FOR UPDATE 必须在显式事务中才生效
autocommit=1 时,SELECT ... FOR UPDATE 执行完立刻提交,锁瞬间释放,等于没锁。常见错误现象是:另一事务能立刻 UPDATE 同一行,以为锁失效了。
- 执行前先确认:
SELECT @@autocommit;返回0才安全;否则先SET autocommit = 0; - 必须搭配
BEGIN或START TRANSACTION使用,不能裸写SELECT ... FOR UPDATE - 在 MyISAM 等非事务引擎、视图、临时表上会报错:
ERROR 1288: The target table of the FOR UPDATE clause cannot be updated
无索引 or 条件不走索引 → 锁全表
这是最常踩的坑。InnoDB 无法定位具体行时,只能退化为表级锁,极大降低并发性。比如 SELECT * FROM user_info_tab WHERE city = '深圳' FOR UPDATE,若 city 没索引,就会锁整张表。
- 检查是否走索引:
EXPLAIN SELECT ... FOR UPDATE,看key列是否为NULL - 唯一索引(如
user_name)+ 精确匹配(=)→ 只锁匹配行 - 普通索引 + 范围查询(
>,BETWEEN)→ 可能锁多行 + 间隙锁(RR 下) - 主键/唯一索引 +
NOT EXISTS或!=→ 可能全表扫描 → 表锁
RR 隔离级别下默认加间隙锁 + 记录锁
MySQL 8.0 默认隔离级别是 REPEATABLE-READ,此时 SELECT ... FOR UPDATE 不只锁已有数据行,还会锁住“本不该存在但可能插入的位置”(间隙),防止幻读。
- 例如:
SELECT * FROM t WHERE name = 'Alice' FOR UPDATE,若name有索引但表中无Alice,仍会锁住该索引间隙 - 间隙锁之间不冲突,但间隙锁与记录锁互斥;
SELECT ... LOCK IN SHARE MODE无法兼容间隙锁,容易引发死锁 - RC 隔离级别下不加间隙锁,只锁命中行(记录锁),幻读可能发生,但锁范围更小、冲突更低
真正难控制的不是“加不加锁”,而是“加在哪、加多大”。索引设计、WHERE 条件写法、隔离级别选择,三者稍有偏差,锁行为就从行级滑向表级——而这个过程没有警告,只有压测时的性能断崖。











