协调行锁与表锁关系的重要机制。它让两种不同粒度的锁能够高效共存。
简单来说,你可以把意向锁理解为一个 “声明”或“预告”。它不是用来锁定具体数据的,而是用来快速判断一张表是否有可能被某个事务用行锁锁定,从而避免低效的全表扫描。
想象一个没有意向锁的场景:
事务A想给表中的某些行加上排他行锁(X Lock)。 同时,事务B想给整个表加上一个排他表锁(例如LOCK TABLES ... WRITE)。要加这个表锁,事务B必须确保当前表中没有任何一行数据被其他事务锁定。
问题来了: 事务B如何知道表中是否有行被锁定呢?它只能笨拙地、逐行检查每一行记录上是否有行锁。对于一张百万级别的表,这种检查是灾难性的,会极度低效。
意向锁的解决方案: 事务A在获得行锁(无论是共享行锁S还是排他行锁X)之前,会先自动、快速地获取一个对应类型的表级意向锁。这样:
意向锁是表级锁,主要分为两种:
意向共享锁(Intention Shared Lock, IS锁)
SELECT ... LOCK IN SHARE MODE 这类语句,需要加行级S锁之前,InnoDB会自动先给表加上一个IS锁。意向排他锁(Intention Exclusive Lock, IX锁)
SELECT ... FOR UPDATE、UPDATE、DELETE、INSERT 这类语句,需要加行级X锁之前,InnoDB会自动先给表加上一个IX锁。理解意向锁的关键在于它的兼容性规则。下表清晰地展示了表级锁(包括意向锁)之间的互斥关系:
| 当前持有的锁(横向) vs 请求的锁(纵向) | X (排他表锁) | IX (意向排他锁) | S (共享表锁) | IS (意向共享锁) |
|---|---|---|---|---|
| X (排他表锁) | ❌ 冲突 | ❌ 冲突 | ❌ 冲突 | ❌ 冲突 |
| IX (意向排他锁) | ❌ 冲突 | ✅ 兼容 | ❌ 冲突 | ✅ 兼容 |
| S (共享表锁) | ❌ 冲突 | ❌ 冲突 | ✅ 兼容 | ✅ 兼容 |
| IS (意向共享锁) | ❌ 冲突 | ✅ 兼容 | ✅ 兼容 | ✅ 兼容 |
记忆要点:
我们通过一个例子来看意向锁如何工作。假设有一张 users 表。
| 时间线 | 事务A | 事务B | 说明 |
|---|---|---|---|
| 1 | START TRANSACTION; |
START TRANSACTION; |
两个事务开始 |
| 2 | SELECT * FROM users WHERE id = 1 FOR UPDATE; |
事务A想锁定id=1的行。系统自动先为表users加上IX锁,然后为id=1的行加上X锁。 |
|
| 3 | LOCK TABLES users WRITE; (或 ALTER TABLE ...) |
事务B想给整张表加排他锁(X)。它会检查表users上的锁,发现已经有一个IX锁存在。根据兼容矩阵,X与IX冲突。因此事务B被阻塞,进入等待状态。 |
|
| 4 | COMMIT; |
事务A提交,释放IX表锁和id=1的行锁。 | |
| 5 | (获得锁) | 表users上的IX锁消失,事务B成功获得表的X锁,继续执行。 |
如果没有意向锁(IS/IX):
在步骤3,事务B无法快速知道表里是否有行锁,它必须去扫描检查users表的每一行(或者所有索引),直到发现id=1的行被锁定,才会阻塞。这个过程非常低效。
你可以通过以下SQL语句查看当前InnoDB的锁信息(需要一定的权限,如PROCESS权限):
-- MySQL 8.0+ 推荐使用
SELECT * FROM performance_schema.data_locks;
-- 或者使用
SELECT * FROM information_schema.INNODB_LOCKS; (在MySQL 5.7及更早版本常用,8.0中已废弃相关表)
SELECT * FROM information_schema.INNODB_LOCK_WAITS;
在输出结果中,LOCK_TYPE 为 TABLE 的锁,其 LOCK_MODE 字段如果显示为 IS 或 IX,那就是意向锁。
意向锁(IS/IX)是事务在获取行锁前,在表级别设置的一个“快速标记”,它使得后续请求表锁的事务无需扫描所有行就能知道是否存在行锁冲突,从而极大提升了表锁判断的效率,是实现多粒度锁协同工作的关键。