MySQL行锁按索引记录加,WHERE不走索引会导致全表扫描并加临键锁,甚至升级为表锁;唯一索引下WHERE id=1仅加记录锁,非唯一索引则加临键锁;ORDER BY+LIMIT是否缩小锁范围取决于是否命中索引。

WHERE 条件没走索引,全表扫描就等于全表加锁
MySQL 的行锁(比如 UPDATE 或 SELECT ... FOR UPDATE)不是按“行”加的,而是按“索引记录”加的。如果 WHERE 条件无法命中索引,InnoDB 只能走聚簇索引全扫描——这时每条记录都会被加上临键锁(next-key lock),锁住整个范围,甚至可能升级为表锁。
- 常见错误现象:
SHOW ENGINE INNODB STATUS里看到大量RECORD LOCKS,但业务只改一行,却卡住其他无关更新 - 使用场景:高并发下单、库存扣减、账户余额变更等对一致性要求高的写操作
- 验证是否走索引:务必用
EXPLAIN看type是否为range/ref/const,且key列非NULL - 注意隐式类型转换:比如
user_id是INT,但传了字符串'123',会导致索引失效 → 全表锁
联合索引顺序错,等于白建
联合索引 (a, b, c) 能加速 WHERE a=1 AND b=2,但对 WHERE b=2 AND c=3 完全无效——不仅查不到,而且加锁也会落到聚簇索引上,锁住所有满足 b=2 的行(即使 c 不匹配)。
- 参数差异:
WHERE a=1锁的是索引中a=1对应的所有索引项;WHERE a=1 AND b>2锁的是(a=1, b>2)的临键区间,范围更小 - 容易踩的坑:把高频过滤字段放在联合索引后位,比如
(created_at, user_id),但查询总以user_id为条件 → 索引失效 → 加锁变宽 - 性能影响:错误顺序会让原本只锁几十行的操作,变成锁几千行,阻塞明显
SELECT ... FOR UPDATE 在唯一索引和非唯一索引下加锁行为完全不同
这是最容易被忽略的细节:同样是 WHERE id = 123,如果 id 是主键或唯一索引,InnoDB 只加一条记录上的记录锁(record lock);但如果 id 是普通索引(哪怕有唯一约束但没定义为 UNIQUE),就会加临键锁(next-key lock),锁住该值前后的间隙。
- 常见错误现象:两个事务分别执行
SELECT * FROM t WHERE idx_col = 5 FOR UPDATE(idx_col是非唯一索引),第二个事务会阻塞——即使查的是同一行,因为锁住了间隙 - 使用场景:需要精确控制并发更新边界时(如防超卖),必须确认索引是否唯一,否则看似单行操作实则锁一片
- 验证方式:用
SELECT * FROM performance_schema.data_locks查LOCK_MODE是REC_NOT_GAP还是NEXT_KEY
ORDER BY + LIMIT 不一定能缩小锁范围
很多人以为 SELECT ... FOR UPDATE ORDER BY id LIMIT 1 只锁 1 行,但前提是 ORDER BY 字段上有索引,且优化器真用了它。否则 MySQL 仍会先扫描多行再排序取 limit,过程中对扫描到的每一行都加锁。
- 性能影响:没索引的
ORDER BY可能让锁从 1 行扩大到几百行,尤其在大表上 - 兼容性注意:MySQL 8.0+ 对
LIMIT下推做了优化,但前提是ORDER BY和WHERE能共用同一个索引;5.7 基本不优化 - 实操建议:如果必须靠
ORDER BY ... LIMIT取最新一条做排他操作,确保ORDER BY字段单独建索引,或与WHERE条件字段组成覆盖索引
真正决定锁多少行的,从来不是 SQL 写得多“精准”,而是优化器最终走了哪条索引路径。看 EXPLAIN 不够,得结合 data_locks 和实际并发表现反推——很多线上死锁,根源都在你以为“只锁一行”的那条语句上。


















