0
0
0

MySQL 学习记录:InnoDB 意向锁 IS/IX 原理与锁兼容性

2026-10-29
2026-10-29
文章摘要
|

一、什么是意向锁?

InnoDB 意向锁(Intention Lock)有两种:

  • IS(Intention Shared Lock):意向共享锁。

  • IX(Intention Exclusive Lock):意向排他锁。

它们都是表级锁,由 InnoDB 自动管理。

锁类型

所在层级

含义

IS

表级

准备或已经对表内某些记录加 S 锁

IX

表级

准备或已经对表内某些记录加 X 锁

S

表级或行级

共享锁模式

X

表级或行级

排他锁模式

重点:意向锁虽然加在表上,但不代表锁住整张表的所有数据。

二、为什么需要意向锁?

假设订单表:

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

结果:通常不冲突。

因为:

  1. 表 IX 与表 IS 兼容。

  2. id=20 的行 X 与 id=30 的行 S 作用于不同记录。

如果事务 B 请求 id=20 的行 S,则会与事务 A 的行 X 冲突。

因此:IX 与表 S 冲突,不代表 IX 与所有行 S 锁都冲突。

五、IS、IX、S、X 锁兼容性

以下兼容性矩阵中,S 和 X 都是表级锁,不是行锁。

已有锁 \ 请求锁

IS

IX

表 S

表 X

IS

兼容

兼容

兼容

冲突

IX

兼容

兼容

冲突

冲突

表 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 行 X

2. SELECT LOCK IN SHARE MODE

MySQL 5.7:

BEGIN;

SELECT *
FROM orders
WHERE id = 20
LOCK IN SHARE MODE;

通常是:

表 IS + id=20 行 S

3. 普通 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。

十、最终总结

只需要记住以下六点:

  1. IS/IX 是表级意向锁,S/X 是共享或排他的锁模式。

  2. S/X 既可能是表锁,也可能是行锁,必须说明锁定层级。

  3. 表 IX 不等于表 X,不会直接禁止其他事务修改不同记录。

  4. IS、IX、表 S、表 X 兼容性矩阵比较的都是表级锁。

  5. IX 与表 S/X 冲突,是因为全表锁要求与局部排他锁意向不兼容。

  6. IX 与 IX 兼容,但相同记录上的两个 X 记录锁冲突。

一句话掌握:IS/IX 用于协调表锁与行锁,表级兼容不代表行级一定兼容;判断冲突时,先明确锁的层级,再判断它们是否作用于相同的锁定对象。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或者给予支持!

评论