Mysql中:IS,IX,S,X锁分别是什么意思?什么时候会加这几种锁,如何共存?

8 阅读5分钟

在 MySQL (特别是 InnoDB 存储引擎) 中,锁机制分为表级锁行级锁。你提到的 IS, IX, S, X 是四种核心的锁模式,它们共同构成了 InnoDB 的多粒度锁协议(Multiple Granularity Locking)。

以下是详细解析:

1. 四种锁的含义

表格

锁模式全称中文名称作用范围含义
SShare Lock共享锁 (读锁)行 / 表允许事务读取一行数据。其他事务也可以获取该行的 S 锁,但不能获取 X 锁。
XExclusive Lock排他锁 (写锁)行 / 表允许事务更新或删除一行数据。其他事务不能获取该行的 S 锁或 X 锁。
ISIntention Share Lock意向共享锁仅表级表示事务打算在表中的某些行上获取 S 锁。它本身不锁住任何行,只是告诉别人:“我要读表里的某些行”。
IXIntention Exclusive Lock意向排他锁仅表级表示事务打算在表中的某些行上获取 X 锁。它告诉别人:“我要修改表里的某些行”。

关键点:意向锁(IS/IX)只存在于表级别不存在于行级别。它们的存在是为了让数据库能快速判断“这张表里有没有行被锁住”,而不需要逐行检查。


2. 什么时候会加这些锁?

(1) S 锁 (共享锁)

  • 触发场景

    • 普通查询:SELECT ... LOCK IN SHARE MODE (MySQL 8.0+ 语法为 SELECT ... FOR SHARE)。
    • 某些隔离级别下的普通 SELECT(如可重复读隔离级别下,快照读不加锁,但当前读可能涉及)。
  • 目的:保证读取期间数据不被修改。

(2) X 锁 (排他锁)

  • 触发场景

    • 写操作:INSERTUPDATEDELETE
    • 显式加锁查询:SELECT ... FOR UPDATE
  • 目的:保证修改期间数据不被读取(脏读)或被其他事务修改。

(3) IS 锁 (意向共享锁)

  • 触发场景

    • 当你在表中的某一行加 S 锁 之前,InnoDB 会自动先在表级别加上 IS 锁
    • 例如:执行 SELECT ... FOR SHARE
  • 流程:先申请表级 IS 锁 -> 成功后 -> 再申请行级 S 锁。

(4) IX 锁 (意向排他锁)

  • 触发场景

    • 当你在表中的某一行加 X 锁 之前,InnoDB 会自动先在表级别加上 IX 锁
    • 例如:执行 UPDATEDELETEINSERT 或 SELECT ... FOR UPDATE
  • 流程:先申请表级 IX 锁 -> 成功后 -> 再申请行级 X 锁。


3. 锁的共存情况 (兼容性矩阵)

这是理解锁机制的核心。如果两个锁兼容,它们可以同时存在;如果不兼容,后请求的锁会被阻塞。

表级锁兼容性矩阵 (针对 IS, IX, 表级 S, 表级 X)

表格

请求锁 \ 当前持有锁ISIXS (表锁)X (表锁)
IS✅ 兼容✅ 兼容✅ 兼容❌ 冲突
IX✅ 兼容✅ 兼容❌ 冲突❌ 冲突
S (表锁)✅ 兼容❌ 冲突✅ 兼容❌ 冲突
X (表锁)❌ 冲突❌ 冲突❌ 冲突❌ 冲突
  • 解读

    • IS 和 IX 互不冲突:因为一个想读行,一个想改行,只要不是改同一行,理论上可以共存(具体行锁会处理细节)。
    • 表级 S 锁与 IX 冲突:如果有人拿了全表的 S 锁(LOCK TABLES t READ),其他人就不能拿 IX 锁(意味着不能去改任何行),反之亦然。
    • 意向锁之间永远兼容这就是意向锁设计的精妙之处。多个事务可以同时声明“我要读行”或“我要改行”,具体的冲突留给行锁去解决,而不需要在表级别就阻塞。

行级锁兼容性矩阵 (针对 S, X)

表格

请求锁 \ 当前持有锁S (行)X (行)
S (行)✅ 兼容❌ 冲突
X (行)❌ 冲突❌ 冲突
  • 解读

    • S + S:大家都能读,没问题。
    • S + X:有人要读,有人要改,冲突。
    • X + X:两个人都要改,冲突。

4. 为什么要设计意向锁 (IS/IX)?

如果没有意向锁,当一个事务想要给整张表加锁(例如 LOCK TABLES ... WRITE,即表级 X 锁)时,数据库必须逐行扫描整张表,检查每一行是否已经被其他事务加了行锁(S 或 X)。如果表有千万行数据,这个检查过程会非常慢。

有了意向锁:

  1. 事务要加表级 X 锁时,只需要检查表上有没有 IS 或 IX 锁。

    • 如果有 IS/IX,说明表里有行被锁住了,直接拒绝表锁请求(阻塞)。
    • 如果没有,说明没有任何行被锁定,可以直接加表锁。
  2. 这极大地提高了判断“能否加表锁”的效率,从 O(N)O(N) 降低到了 O(1)O(1) 。

5. 总结示例

假设事务 A 执行了 SELECT ... FOR UPDATE (锁定 ID=1 的行):

  1. 步骤 1:事务 A 向 InnoDB 申请 表级 IX 锁

    • 检查:表上是否有冲突的锁(如表级 S 锁 or 表级 X 锁)?如果没有,授予 IX 锁。
  2. 步骤 2:事务 A 向 InnoDB 申请 行级 X 锁 (针对 ID=1)。

    • 检查:ID=1 这一行是否有冲突的锁?如果没有,授予 X 锁。

此时,如果事务 B 想执行 LOCK TABLES t WRITE (申请表级 X 锁):

  • InnoDB 检查表锁兼容性:发现表上已经有 IX 锁 (由事务 A 持有)。
  • 查表可知:IX 与 表级 X 冲突
  • 结果:事务 B 被阻塞,无需扫描每一行就知道表不可用。

如果事务 C 想执行 SELECT ... FOR UPDATE (锁定 ID=2 的行):

  • 申请 表级 IX 锁:与事务 A 的 表级 IX 锁 兼容 -> 成功。
  • 申请 行级 X 锁 (针对 ID=2):该行无锁 -> 成功。
  • 结果:事务 A 和 C 同时运行,互不干扰(因为它们锁的是不同的行)。

如果事务 D 想执行 SELECT ... FOR UPDATE (锁定 ID=1 的行):

  • 申请 表级 IX 锁:兼容 -> 成功。
  • 申请 行级 X 锁 (针对 ID=1):发现事务 A 已经持有了 行级 X 锁
  • 查行锁矩阵:X 与 X 冲突
  • 结果:事务 D 在行级别被阻塞。