协调行锁与表锁关系的重要机制。它让两种不同粒度的锁能够高效共存。
简单来说,你可以把意向锁理解为一个 “声明”或“预告”。它不是用来锁定具体数据的,而是用来快速判断一张表是否有可能被某个事务用行锁锁定,从而避免低效的全表扫描。
一、为什么需要意向锁?
想象一个没有意向锁的场景:
事务A想给
表中的某些行加上排他行锁(X Lock)。
同时,事务B想给
整个表加上一个排他表锁(例如
LOCK TABLES ... WRITE)。要加这个表锁,事务B必须确保当前表中没有任何一行数据被其他事务锁定。
问题来了: 事务B如何知道表中是否有行被锁定呢?它只能笨拙地、逐行检查每一行记录上是否有行锁。对于一张百万级别的表,这种检查是灾难性的,会极度低效。
意向锁的解决方案:
事务A在获得行锁(无论是共享行锁S还是排他行锁X)之前,会先自动、快速地获取一个对应类型的表级意向锁。这样:
- 当事务B想加表锁时,它只需要看一眼表级别的意向锁(瞬间完成),就知道:“哦,这张表里已经有事务持有行锁了,我不能直接加表锁”,从而立即进入等待或失败,无需扫描任何行。
二、意向锁的类型与兼容性
意向锁是表级锁,主要分为两种:
意向共享锁(Intention Shared Lock, IS锁)
- 含义:事务打算在某些行上加共享锁(S Lock)。
- 获取时机:在执行
SELECT ... LOCK IN SHARE MODE 这类语句,需要加行级S锁之前,InnoDB会自动先给表加上一个IS锁。
意向排他锁(Intention Exclusive Lock, IX锁)
- 含义:事务打算在某些行上加排他锁(X Lock)。
- 获取时机:在执行
SELECT ... FOR UPDATE、UPDATE、DELETE、INSERT 这类语句,需要加行级X锁之前,InnoDB会自动先给表加上一个IX锁。
核心:锁的兼容性矩阵
理解意向锁的关键在于它的兼容性规则。下表清晰地展示了表级锁(包括意向锁)之间的互斥关系:
| 当前持有的锁(横向) vs 请求的锁(纵向) |
X (排他表锁) |
IX (意向排他锁) |
S (共享表锁) |
IS (意向共享锁) |
|---|
| X (排他表锁) |
❌ 冲突 |
❌ 冲突 |
❌ 冲突 |
❌ 冲突 |
| IX (意向排他锁) |
❌ 冲突 |
✅ 兼容 |
❌ 冲突 |
✅ 兼容 |
| S (共享表锁) |
❌ 冲突 |
❌ 冲突 |
✅ 兼容 |
✅ 兼容 |
| IS (意向共享锁) |
❌ 冲突 |
✅ 兼容 |
✅ 兼容 |
✅ 兼容 |
记忆要点:
- 意向锁之间都是兼容的(IX和IS兼容,IS和IS兼容,IX和IX兼容)。因为它们只是“意向”,表示“我想在部分行上加锁”,不同事务可以在不同行上加锁,所以意向不冲突。
- 意向锁只与全表锁冲突:
- IX 与 S 冲突:一个事务打算改某些行(IX),另一个事务想读整个表(S),不允许。因为读全表要求内容稳定,而行修改会影响内容。
- IS 与 X 冲突:一个事务打算读某些行(IS),另一个事务想改整个表(X),不允许。
- IX 与 X 冲突:一个事务打算改某些行(IX),另一个事务想改整个表(X),不允许。
- IS 与 S 兼容:一个事务打算读某些行(IS),另一个事务想读整个表(S),可以。因为都是读操作,不改变数据。
三、工作流程示例
我们通过一个例子来看意向锁如何工作。假设有一张 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的行被锁定,才会阻塞。这个过程非常低效。
四、重要特性总结
自动施加:意向锁由InnoDB存储引擎
自动管理,无需程序员手动干预。你执行DML或特定SELECT语句时,引擎会自动先加意向锁,再加行锁。
不会阻塞行锁:
- 意向锁(IS, IX)之间是兼容的。
- 行级锁(S, X)的冲突判断只发生在行级别。
- 所以,意向锁的存在不会影响多个事务对表中不同行进行加锁操作(这是并发的基础)。
阻塞表锁:意向锁的主要目的就是
让表级锁(S, X)的请求能够被快速、高效地判断是否可行,避免冲突的表锁请求浪费资源。
锁的层次结构:意向锁体现了数据库的
多粒度锁机制。锁的粒度从上到下是:
表级锁(包括意向锁) -> 页级锁 -> 行级锁。高层级锁(如表锁)的请求需要先检查低层级锁(如意向锁)的冲突情况。
五、如何查看意向锁?
你可以通过以下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)是事务在获取行锁前,在表级别设置的一个“快速标记”,它使得后续请求表锁的事务无需扫描所有行就能知道是否存在行锁冲突,从而极大提升了表锁判断的效率,是实现多粒度锁协同工作的关键。