mysql行锁不会主动升级为表锁,所谓“升级”实为未走索引导致全表扫描并逐行加x锁;核心在于执行计划是否命中索引,而非sql字面意图。

MySQL中行锁不会“主动升级”为表锁,所谓“升级”其实是因查询未走索引导致全表扫描,InnoDB被迫对所有聚簇索引记录加X锁——效果等同于表锁,但底层仍是行锁机制。模拟的关键不是写个LOCK TABLES,而是制造「本想锁一行、实际锁全表」的错觉。
用无索引条件触发全表扫描加锁
这是最常见也最容易复现的“伪升级”场景。InnoDB只在通过索引定位时才加精确行锁;一旦WHERE条件无法命中索引(或索引失效),就会遍历所有行并逐个加X锁。
- 建表时确保目标字段没索引:
CREATE TABLE t_lock (id INT PRIMARY KEY, name VARCHAR(20)); - 插入10条以上数据,比如
INSERT INTO t_lock VALUES (1,'a'),(2,'b'),...,(10,'j'); - 客户端A执行:
BEGIN; UPDATE t_lock SET name='x' WHERE name='a';—— 因name无索引,InnoDB会对全部10行加X锁 - 客户端B立刻执行:
UPDATE t_lock SET name='y' WHERE id=5;—— 会被阻塞,即使id是主键索引,因为全表X锁已存在
用隐式类型转换让索引失效
字段类型和查询值类型不一致时,MySQL会做隐式转换,导致索引无法使用,进而触发全表扫描加锁。这种场景在业务中极隐蔽,但复现成本极低。
- 假设
name字段是VARCHAR,但查询时传了数字:UPDATE t_lock SET name='z' WHERE name=123; - MySQL会把每行
name转成数字再比对(如'abc'→0),无法用索引 - 即使你后来给
name加了索引,这条语句依然全表加锁 - 验证方式:执行前先看
EXPLAIN SELECT * FROM t_lock WHERE name=123;,如果type是ALL,就稳了
用SELECT ... FOR UPDATE配合非索引条件观察阻塞链
相比UPDATE,显式加锁更利于观察锁行为,且能避免事务自动提交干扰。
- 客户端A:
BEGIN; SELECT * FROM t_lock WHERE name='a' FOR UPDATE;(name无索引) - 此时InnoDB已对所有行加X锁,但尚未修改数据,锁状态更“干净”
- 客户端B执行任何涉及
t_lock的写操作(如INSERT、UPDATE、DELETE)都会被阻塞 - 用
SHOW ENGINE INNODB STATUS\G查TRANSACTIONS部分,能看到lock struct(s)数量远超1行
为什么LOCK TABLES不是正解?
手动执行LOCK TABLES t_lock WRITE;确实能锁整张表,但它和InnoDB行锁机制无关,属于Server层表锁,会阻塞所有并发读(包括SELECT),且与事务隔离级别无关。真实业务中几乎不用,也不反映InnoDB的锁行为逻辑。
-
LOCK TABLES后,其他连接连SELECT * FROM t_lock都会被挂起,而“伪升级”下普通SELECT仍可读(除非加FOR UPDATE) - InnoDB内部没有“升级开关”,只有“是否能走索引”这一判断依据
- 真正影响锁范围的,是
WHERE条件能否命中索引,而不是事务里操作了多少行
最易被忽略的一点:锁范围取决于**执行计划**,而非SQL字面意思。哪怕你只更新1行,只要优化器选了全表扫描,就等于锁了全部。所以模拟前务必用EXPLAIN确认索引是否真的生效。











