行锁会在索引失效时退化为表锁,如LIKE左模糊、隐式类型转换、函数操作字段等导致全表扫描;此时InnoDB加意向锁并实际锁全表。

行锁在什么情况下会退化成表锁
MySQL 的 InnoDB 默认用行锁,但不是所有带索引的 WHERE 条件都能命中行锁。一旦优化器判定无法使用索引、或用了不走索引的条件(比如 LIKE '%abc'、隐式类型转换、函数包裹字段),就会触发全表扫描,进而升级为表级意向锁 + 实际的表锁行为。
常见退化场景:
-
UPDATE user SET status=1 WHERE name LIKE '%admin%'——name有索引但LIKE左模糊导致索引失效 -
SELECT * FROM order WHERE user_id = '123',而user_id是INT类型,传入字符串'123'触发隐式转换,索引失效 - 事务中执行了未提交的
SELECT ... FOR UPDATE,但查询条件没走索引,InnoDB 会对所有聚集索引记录加锁,效果等同于锁表
INSERT … ON DUPLICATE KEY UPDATE 用的是行锁还是表锁
这个语句本质是「先尝试插入,冲突则更新」,InnoDB 对它做了专门优化:只对将要插入/更新的那几行加 next-key lock(间隙锁 + 行锁),不会锁整张表。
但要注意前提:
- 必须有
PRIMARY KEY或UNIQUE KEY,否则冲突判断无依据,语句会报错,更谈不上加什么锁 - 冲突发生在唯一索引上时,InnoDB 会在该唯一索引对应记录上加
record lock;如果插入位置存在间隙,还会额外加gap lock - 如果语句里
UPDATE部分修改了唯一索引字段本身(比如UPDATE id = id + 1),可能导致锁范围扩大,甚至触发死锁
如何用 SELECT … FOR UPDATE 精确控制行锁范围
SELECT ... FOR UPDATE 是否锁住多行、是否锁间隙,完全取决于你的 WHERE 条件是否能走索引,以及索引类型。
实操建议:
- 只对主键或唯一索引字段做等值查询(如
WHERE id = 100),锁的是单条记录的record lock - 对非唯一索引等值查询(如
WHERE status = 1),InnoDB 会加next-key lock,即锁住匹配记录 + 该记录前的间隙,防止幻读 - 范围查询(如
WHERE created_at > '2024-01-01')一定包含间隙锁,范围越大,锁越多,容易阻塞其他事务 - 想避免间隙锁?可临时把事务隔离级别降为
READ COMMITTED(需确认业务能否接受不可重复读)
SHOW ENGINE INNODB STATUS 能看到哪些锁信息
这是诊断锁问题最直接的命令,输出里 TRANSACTIONS 部分会列出当前阻塞关系和持锁情况。重点关注:
-
Trx id和Trx mysql thread id:定位具体哪个线程卡住了 -
Trx state: LOCK WAIT表示正在等锁;Trx state: RUNNING不代表没锁,只是当前没在等 -
lock_mode X locks rec but not gap:说明是纯行锁;lock_mode X locks gap before rec是间隙锁;lock_mode X单独出现大概率是表锁或意向锁 -
Record lock, heap no 123中的heap no是聚簇索引堆记录号,结合SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX可交叉验证
mysql> SHOW ENGINE INNODB STATUS\G ... ---TRANSACTION 42156789, ACTIVE 12 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s) MySQL thread id 123, OS thread handle 140234567890176, query id 456789 localhost root updating UPDATE account SET balance = balance - 100 WHERE id = 5
上面这段输出里,“locked 1” 是指锁了一张表(逻辑),但实际是这张表里的某一行被 X 锁住了——粒度得看后面的 row lock(s) 数量和类型,不能只看 locked 1 就认为是表锁。
锁粒度不是写死的,它由查询路径、索引结构、隔离级别共同决定。很多人调优时只改 SQL 写法,却忘了检查执行计划是否真走了索引,或者忽略了 autocommit=0 下事务长期不提交导致锁一直挂着——这才是并发下锁问题最常被忽略的根因。










