mysql中锁的升级与降级:行锁与表锁的转换

来源:这里教程网 时间:2026-02-28 20:44:15 作者:

MySQL 什么时候会把行锁升级成表锁?

InnoDB 默认使用行锁,但锁升级不是 MySQL 主动“升级”的策略行为,而是由事务实际加锁范围和优化器判断共同导致的隐式结果。真正触发表级锁定的常见场景是:扫描了大量行却无法使用索引,此时优化器可能放弃行锁而直接对整个表加意向锁 + 实际表锁(如

LOCK TABLES
或隐式锁升级),或更常见的是——在唯一索引失效时,退化为间隙锁+临键锁组合,最终覆盖全表范围

执行
UPDATE t SET a=1 WHERE b LIKE '%abc%'
,且
b
没有索引 → 全表扫描 → 每行都加行锁 → 事务持有大量行锁 → 极易触发死锁或被检测为高开销 → InnoDB 可能拒绝执行或由客户端重试失败
显式使用
LOCK TABLES t WRITE
→ 直接获得表锁,此时所有行锁请求都会被阻塞
AUTO_INCREMENT
列插入时,若未走主键/唯一索引查找路径,InnoDB 会对整个索引段加锁,效果接近表锁

为什么没有“锁降级”这回事?

InnoDB 的锁机制不支持运行时将表锁降为行锁。所谓“降级”,其实是事务结束、锁自动释放后,后续语句重新按需申请更细粒度锁的过程。它不是同一个事务内锁类型的转换,而是新事务的独立加锁行为。

一个事务执行了
LOCK TABLES t WRITE
,它持有的就是表级锁,直到
UNLOCK TABLES
或会话断开
该事务内部再执行
SELECT ... FOR UPDATE
不会“降级”为行锁,而是直接报错:
ERROR 1100 (HY000): Table 't' was not locked with LOCK TABLES
真正看起来像“降级”的现象,往往是因为前一个大事务释放了表锁,新事务用带索引条件的
UPDATE
只锁几行 → 这是两个事务各自的行锁行为,非锁类型转换

如何避免意外的表级锁定?

核心思路是让每条 DML 都命中索引,并控制扫描范围。锁是否“升级”,本质是优化器是否选择走索引 + 是否允许使用 MVCC 快照读来绕过加锁。

确保
WHERE
条件字段上有合适索引(注意最左前缀、隐式类型转换会导致索引失效)
避免在大表上执行无
LIMIT
UPDATE/DELETE
,尤其带函数或模糊匹配(如
UPPER(col)
,
col LIKE '%x'
EXPLAIN
确认执行计划是否走了索引:
type
字段不能是
ALL
index
(全索引扫描也可能引发大量行锁)
批量更新改成分页小事务:
UPDATE t SET status = 1 WHERE id BETWEEN 1000 AND 1099 AND status = 0;

意向锁是锁升级的中间态吗?

不是。意向锁(

IS
/
IX
)是元数据锁,用于表明“我将在某行/某些行上加锁”,它本身不阻塞任何操作,只和其他意向锁或表锁冲突。它的存在是为了快速判断是否可以加表锁,而非行锁与表锁之间的过渡状态。

事务 A 对某行加
X
行锁 → 自动给表加
IX
意向排他锁
此时事务 B 执行
LOCK TABLES t WRITE
→ 需要
X
表锁 → 与
IX
冲突 → 被阻塞
但这个阻塞不是因为“行锁升成了表锁”,而是因为表锁请求和已有意向锁不兼容 锁升级不是配置项,也不是可编程控制的行为;它是执行路径、索引有效性、事务大小三者共同作用下的副作用。最容易被忽略的一点:即使 SQL 看似只改一行,只要
WHERE
条件没走索引,InnoDB 就得扫全表——那一行的锁,是从几千个行锁里“幸存”下来的,而不是单独加上的。

相关推荐