MySQL 学习记录:InnoDB 意向锁 IS/IX 原理与锁兼容性
一、什么是意向锁?
InnoDB 意向锁(Intention Lock)有两种:
IS(Intention Shared Lock):意向共享锁。
IX(Intention Exclusive Lock):意向排他锁。
它们都是表级锁,由 InnoDB 自动管理。
重点:意向锁虽然加在表上,但不代表锁住整张表的所有数据。
二、为什么需要意向锁?
假设订单表:
orders
├── id=10
├── id=20
└── id=30事务 A:
BEGIN;
UPDATE orders
SET status = 2
WHERE id = 20;假设通过主键等值定位,事务 A 通常获取:
orders 表 → IX 意向锁
id=20 → X 记录锁IX 的作用是告诉锁管理器:
这张表内有记录正在或准备被排他锁定。
如果另一个事务想获取整张表的 S/X 锁,InnoDB 可以通过表级意向锁判断是否冲突,不需要逐条检查表内的行锁。
因此:
意向锁的核心作用是协调表级锁和行级锁。
三、表 X 锁与行 X 锁的区别
这是本次学习最重要的易错点。
1. 行 X 锁
事务 A:
表 IX + id=20 行 X
事务 B:
表 IX + id=30 行 X两个事务可以同时修改不同记录,因为:
IX 和 IX 兼容。
两个 X 记录锁作用于不同记录。
所以,一张表里可以同时存在多个行 X 锁。
2. 表 X 锁
表 X 锁要求独占整张表的锁定范围。
例如事务 A 已有:
表 IX + id=20 行 X事务 B 请求:
orders 表 X 锁结果:冲突。
原因:事务 A 已经在表内持有排他记录锁,事务 B 不能同时独占整张表。
四、为什么 IX 和表 S 冲突?
假设事务 A:
BEGIN;
UPDATE orders
SET status = 2
WHERE id = 20;获得:
orders 表:IX
id=20:行 X事务 B 请求:
orders 表:S 表锁这里的表 S 锁意味着:
我要共享锁定整张表,不允许其他事务在表中持有与共享锁冲突的排他锁。
但事务 A 已经准备或正在对 id=20 使用排他锁。
所以:
IX + 表 S = 冲突。
为什么 IX 和行 S 不一定冲突?
事务 A:
表 IX + id=20 行 X事务 B:
表 IS + id=30 行 S结果:通常不冲突。
因为:
表 IX 与表 IS 兼容。
id=20 的行 X 与 id=30 的行 S 作用于不同记录。
如果事务 B 请求 id=20 的行 S,则会与事务 A 的行 X 冲突。
因此:IX 与表 S 冲突,不代表 IX 与所有行 S 锁都冲突。
五、IS、IX、S、X 锁兼容性
以下兼容性矩阵中,S 和 X 都是表级锁,不是行锁。
理解规律
IS + IX 兼容
双方仅声明准备对表内部分记录加锁,具体是否冲突要继续检查行锁。
IX + IX 兼容
允许不同事务同时修改表内不同记录。
IX + 表 S 冲突
表 S 要求全表共享保护,不能与其他事务的局部排他写锁共存。
IX + 表 X 冲突
表 X 要求独占整张表,不能与其他事务的 IX 共存。
IS + 表 S 兼容
局部共享与全表共享的要求一致。
六、通过 SQL 理解实际加锁
1. SELECT FOR UPDATE
BEGIN;
SELECT *
FROM orders
WHERE id = 20
FOR UPDATE;主键等值命中已有记录时,通常是:
表 IX + id=20 行 X2. SELECT LOCK IN SHARE MODE
MySQL 5.7:
BEGIN;
SELECT *
FROM orders
WHERE id = 20
LOCK IN SHARE MODE;通常是:
表 IS + id=20 行 S3. 普通 SELECT
SELECT *
FROM orders
WHERE id = 20;在 RC/RR 下通常属于 MVCC 快照读,不会因为普通一致性读而获取上述 S 记录锁。
4. UPDATE
BEGIN;
UPDATE orders
SET status = 2
WHERE id = 20;通常是:
表 IX + id=20 行 X注意:以上例子假设主键索引等值命中,不涉及额外的索引范围锁、外键等情况。实际加锁范围与执行计划、索引和隔离级别有关。
七、MySQL 5.7 锁排查
1. 查看当前事务
SELECT
trx_id,
trx_state,
trx_started,
trx_mysql_thread_id,
trx_query
FROM information_schema.INNODB_TRX;2. 查看锁等待关系
SELECT
r.trx_id AS waiting_trx_id,
r.trx_query AS waiting_sql,
b.trx_id AS blocking_trx_id,
b.trx_query AS blocking_sql
FROM information_schema.INNODB_LOCK_WAITS w
JOIN information_schema.INNODB_TRX r
ON w.requesting_trx_id = r.trx_id
JOIN information_schema.INNODB_TRX b
ON w.blocking_trx_id = b.trx_id;3. 查看 InnoDB 锁状态
SHOW ENGINE INNODB STATUS\G可能看到:
TABLE LOCK table `test`.`orders`
lock mode IX表示事务对 orders 表持有 IX 意向锁,不代表整张表已经被排他锁锁住。
注意:MySQL 5.7 的 INNODB_LOCKS 主要显示涉及锁等待的锁,不是全部已持有锁的完整清单。
八、Java 8 + Spring Boot 生产场景
@Transactional
public void updateOrder(Long id, Integer status) {
orderMapper.updateStatus(id, status);
}假设两个线程并发执行:
// 线程 A
updateOrder(20L, 2);
// 线程 B
updateOrder(30L, 3);通常可以并发修改,因为:
线程 A:表 IX + id=20 行 X
线程 B:表 IX + id=30 行 X如果两个线程都更新 id=20,则两个 IX 仍然兼容,但行 X 锁发生冲突,其中一个事务需要等待。
生产排查重点:看到 IX 锁不等于整表被锁,应继续定位具体的记录锁和阻塞事务。
九、面试常见问题
1. IS/IX 是表锁还是行锁?
表级意向锁,但不直接锁住整张表的所有数据。
2. 一张表能同时有多个 X 记录锁吗?
可以。不同事务可以持有不同记录上的 X 锁。
3. 为什么 IX 与 IX 兼容?
因为两个事务可以同时准备修改表内不同的记录;是否发生冲突由具体记录锁决定。
4. 为什么 IX 与表 S 冲突?
IX 表示存在或准备获取局部排他锁,而表 S 要求整个表处于共享锁保护下,两种要求不兼容。
5. 为什么 IX 与行 S 不一定冲突?
IX 只负责表级意向声明;不同记录上的行 S/X 锁可能没有冲突。
6. 意向锁需要手动加吗?
一般不需要。InnoDB 在获取相应行锁时自动获取 IS 或 IX。
十、最终总结
只需要记住以下六点:
IS/IX 是表级意向锁,S/X 是共享或排他的锁模式。
S/X 既可能是表锁,也可能是行锁,必须说明锁定层级。
表 IX 不等于表 X,不会直接禁止其他事务修改不同记录。
IS、IX、表 S、表 X 兼容性矩阵比较的都是表级锁。
IX 与表 S/X 冲突,是因为全表锁要求与局部排他锁意向不兼容。
IX 与 IX 兼容,但相同记录上的两个 X 记录锁冲突。
一句话掌握:IS/IX 用于协调表锁与行锁,表级兼容不代表行级一定兼容;判断冲突时,先明确锁的层级,再判断它们是否作用于相同的锁定对象。
