SELECT FOR UPDATE必然锁整张表当WHERE条件未走索引;InnoDB因全表扫描对所有聚簇索引记录加X锁,RR下等效表级锁定,即使含LIMIT 1亦不改变锁范围。

SELECT FOR UPDATE 会升级为表锁,不是“偶尔”,而是只要 WHERE 条件没走索引,就必然锁整张表。
WHERE 条件没走索引 → 直接锁全表
InnoDB 的行锁是加在索引上的,不是加在数据行上。当 SELECT ... FOR UPDATE 的 WHERE 条件无法命中任何有效索引(比如字段无索引、用了函数、发生隐式类型转换、LIKE '%abc'),InnoDB 就只能全表扫描,对所有聚簇索引记录加 X 锁 —— 在 RR 隔离级别下,这等价于事实上的表级锁定。
- 典型错误:
SELECT * FROM users WHERE SUBSTRING(phone, 1, 3) = '138' FOR UPDATE(函数导致索引失效) - 隐式转换:
WHERE user_id = '123'(user_id是INT,传字符串会放弃索引) - 前导通配符:
WHERE name LIKE '%张%'必然全表扫描 - 检查方法:执行
EXPLAIN SELECT ... FOR UPDATE,看key是否为NULL,type是否为ALL
即使加了 LIMIT 1,没索引照样锁表
很多人以为加了 LIMIT 1 就能“只锁一行”,这是错觉。MySQL 不会在扫描前预判哪一行匹配,它必须先扫完整个表(或索引)才能找到第一条符合条件的记录 —— 扫描过程中所有经过的行都会被加锁。所以 SELECT * FROM t WHERE city = '深圳' LIMIT 1 FOR UPDATE,若 city 没索引,仍是全表锁。
- 验证方式:在一个会话中执行该语句并保持事务不提交;另一个会话尝试
UPDATE t SET x=1 WHERE id = 999999(任意未被查询覆盖的主键),若被阻塞,说明已锁表 - 注意:
LIMIT不影响锁范围,只影响返回结果集大小
RR 隔离级别下,普通索引 + 范围查询可能扩大锁范围
在默认的 REPEATABLE-READ 隔离级别下,SELECT ... FOR UPDATE 使用 Next-Key Lock(记录锁 + 间隙锁)。这意味着即使有索引,只要条件是范围(>、BETWEEN、LIKE 'abc%'),锁住的就不止匹配行,还包括它们之间的间隙 —— 实际锁住的可能是几十甚至上百个索引位置。
- 例如:
SELECT * FROM orders WHERE created_at > '2026-04-01' FOR UPDATE,若created_at是普通索引,且该范围内有大量记录,锁范围会远超预期 - 对比:
READ-COMMITTED下只加记录锁,不加间隙锁,锁更轻,但允许幻读 - 唯一索引等值查询(如
WHERE user_name = 'alice')可避免间隙锁,只锁单行
真正难控的不是“会不会锁”,而是“锁在哪、锁多大”。索引是否生效、WHERE 写法是否触发隐式转换、隔离级别是否被忽略 —— 这三者中任一偏差,都可能让本该毫秒级响应的并发操作,变成全表阻塞的线上事故。上线前务必用 EXPLAIN FORMAT=JSON 看 key_locks 和 access_type,而不是靠经验猜。


















