本页内容受版权保护 · 已添加水印 · 禁止任何形式转载
事务与并发(ACID / 隔离级别 / 锁 / MVCC) · 锁机制

什么是意向锁(Intention Lock)?IS / IX 锁如何避免行锁与表锁冲突?

What is an Intention Lock? How do IS / IX locks avoid conflicts between row locks and table locks

答案语言

考察要点

这道题面试官想看你是否深刻理解 InnoDB 的多粒度锁(Multi-Granularity Locking)机制。核心考察点在于:

  1. 问题根源:你是否明白为什么需要一种机制来协调表锁和行锁,即如果没有意向锁,检查锁冲突的成本有多高。
  2. 核心作用:你是否能准确描述意向锁作为一种“信号”或“标志”,其核心价值在于快速判断是否存在表级和行级的锁冲突,从而避免遍历全表。
  3. 锁兼容性:你是否熟知意向锁(IS/IX)与共享锁(S)/排他锁(X)之间的兼容关系,这是理解并发控制的关键。

核心概念

意向锁(Intention Lock)是 InnoDB 存储引擎中的一种表级锁,它的存在是为了协调更高粒度的表锁与更低粒度的行锁之间的冲突。意向锁本身不直接锁定任何数据,而是表明一个事务意图在稍后对表中的某些行加 S 锁或 X 锁。

1. 为什么需要意向锁?

想象一个没有意向锁的场景:

  • 事务 T1 对表 t 中的某一行 r 加了行级排他锁(X 锁)。
    sql
    1-- T1
    2SELECT * FROM t WHERE id = 10 FOR UPDATE; -- 对 id=10 的行加 X 锁
  • 事务 T2 此时想对整个表 t 加表级共享锁(S 锁)。
    sql
    1-- T2
    2LOCK TABLES t READ; -- 尝试对表 t 加 S 锁

为了保证数据一致性,T2 必须检查表 t 中是否存在任何行级排他锁(X 锁),因为表级 S 锁与行级 X 锁是冲突的。在没有意向锁的情况下,T2 唯一的办法就是遍历表 t 的每一行,检查是否存在行锁。如果表 t 有上亿行数据,这个检查过程的开销将是灾难性的,并发性能会急剧下降。

意向锁的解决方案: 引入意向锁后,流程变为:

  1. T1 在对行 r 加 X 锁之前,必须先在表 t 上加一个意向排他锁(IX)。
  2. T2 尝试对表 t 加 S 锁时,它不需要遍历所有行,只需要检查表 t 上是否存在意向排他锁(IX)。
  3. 因为 T1 已经持有了表 t 的 IX 锁,而表级 S 锁与 IX 锁是冲突的,所以 T2 的加锁请求会被阻塞。

这个检查从 O(N)O(N)(N为行数)的复杂度降低到了 O(1)O(1),极大地提升了并发性能。

2. 意向锁的类型

意向锁分为两种,均是表级锁:

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

    • 含义:事务打算对表中的某些行设置共享锁(S 锁)。
    • 触发 SQL:SELECT ... LOCK IN SHARE MODE;
    • 加锁过程:在对行加 S 锁之前,必须先获得该表的 IS 锁。
  • 意向排他锁 (Intention Exclusive Lock, IX)

    • 含义:事务打算对表中的某些行设置排他锁(X 锁)。
    • 触发 SQL:SELECT ... FOR UPDATE;, INSERT, UPDATE, DELETE。
    • 加锁过程:在对行加 X 锁之前,必须先获得该表的 IX 锁。

3. 锁兼容性矩阵

这是理解意向锁如何工作的核心。下表展示了各种锁类型之间的兼容关系(行代表已持有的锁,列代表请求的锁):

已持有 \ 请求X (表级)S (表级)IX (表级)IS (表级)
X (表级)冲突冲突冲突冲突
S (表级)冲突兼容冲突兼容
IX (表级)冲突冲突兼容兼容
IS (表级)冲突兼容兼容兼容

核心解读:

  1. 意向锁之间互相兼容 (IX-IX, IS-IS, IX-IS):两个不同的事务可以同时持有对同一张表的 IX 锁,因为它们可能意图锁定的是不同的行,这并不会立即产生冲突。真正的冲突发生在它们试图锁定同一行时,由行锁来处理。
  2. 意向锁与表锁的冲突关系:
    • 请求表级 S 锁时,必须不能有事务持有该表的表级 X 锁或意向排他锁 (IX)。因为 IX 锁意味着有事务正在或将要修改行,这与读取整个表的快照是冲突的。
    • 请求表级 X 锁时,必须不能有事务持有该表的任何锁(X, S, IX, IS)。

常见低分回答

  1. 混淆意向锁和行锁:“IX 锁是加在行上的排他锁”。

    • 错误点:这是概念性错误。IX/IS 锁是表级锁,它们只是一个意图的标志,而不是作用于具体数据行的锁。真正的行锁是 Record Lock, Gap Lock, Next-Key Lock。
  2. 认为意向锁会阻塞其他意向锁:“一个事务拿了表的 IX 锁,另一个事务就不能再拿 IX 锁了”。

    • 错误点:从兼容性矩阵可知,意向锁之间是完全兼容的。这正是允许多个事务并发更新表中不同行的基础。如果意向锁互斥,那么多粒度锁就失去了意义,退化成了表锁。
  3. 只说定义,不说“为什么”:“意向锁就是 IS 和 IX 两种,分别在 SELECT...SHARE MODE 和 FOR UPDATE 时加”。

    • 错误点:这样的回答只停留在表面背诵,没有展现出对问题本质的理解。高分回答必须解释意向锁是为了解决什么问题(避免全表扫描检查锁冲突),以及它是如何解决的(通过 O(1)O(1) 的表级锁状态检查)。
  4. 无法解释 LOCK TABLES 和意向锁的关系:当被问到 LOCK TABLES t WRITE 为何会和 SELECT ... FOR UPDATE 冲突时,无法清晰地解释是因为前者请求表级 X 锁,后者持有表级 IX 锁,而 X 和 IX 在表级别是冲突的。

工程实践与优化

  • 隐式加锁:在日常的 INSERT, UPDATE, DELETE 操作中,InnoDB 会自动为我们加上 IX 锁,开发者通常无需关心。SELECT ... FOR UPDATE / SHARE MODE 也是如此。
  • 避免显式表锁:在 InnoDB 中,应极力避免使用 LOCK TABLES。这会严重降低并发性,因为它会与所有意图修改或读取行的事务发生冲突。InnoDB 的行级锁和 MVCC 机制已经能很好地处理并发,LOCK TABLES 往往是性能杀手。
  • 问题排查:当遇到死锁或长时间的锁等待时,可以通过 SHOW ENGINE INNODB STATUS 命令来查看锁信息。在输出的 LATEST DETECTED DEADLOCK 或 TRANSACTIONS 部分,你会看到类似 lock_mode IX, lock_mode IS waiting 的信息。这能帮助你理解事务正在等待什么锁,以及是哪个事务持有了冲突的锁。例如,看到一个事务在等待 lock type TABLE lock_mode S,而阻塞它的事务持有 lock_mode IX,就能快速定位是表锁与行锁意向发生了冲突。
  • DDL 操作的锁:ALTER TABLE 等 DDL 操作通常需要获取表上的元数据锁(Metadata Lock),这会等待所有持有该表意向锁的事务结束。因此,在一个高并发更新的表上执行 DDL,很容易导致长时间等待,甚至阻塞整个业务。通常需要选择业务低峰期执行,或使用 pt-online-schema-change、gh-ost 等在线 DDL 工具。

常见追问与延伸题目

  1. 追问:除了 IS/IX 锁,InnoDB 中还有哪些重要的锁?它们分别解决了什么问题?

    • 回答要点:
      • Record Lock (记录锁):锁定单个索引记录,这是最基本的行锁。
      • Gap Lock (间隙锁):锁定一个开区间范围,但不包括记录本身。它解决了可重复读(RR)隔离级别下的“幻读”问题,防止其他事务在这个范围内 INSERT 新数据。
      • Next-Key Lock (临键锁):Record Lock + Gap Lock 的组合,锁定一个左开右闭的区间。这是 InnoDB 在 RR 级别下默认的行锁算法,既锁定了记录,也锁定了记录前的间隙。
      • AUTO-INC Lock (自增锁):一种特殊的表级锁,用于并发插入时生成连续的自增 ID。在 MySQL 8.0 中通过引入更轻量级的互斥量(mutex)进行了优化,提高了并发插入性能。
  2. 追问:MyISAM 存储引擎的锁机制是怎样的?和 InnoDB 的意向锁机制有何根本区别?

    • 回答要点:
      • MyISAM:只支持表级锁。当一个会话 SELECT 时,它会对所有涉及的表加读锁(共享锁);当 UPDATE 或 INSERT 时,它会对表加写锁(排他锁)。
      • 根本区别:MyISAM 不支持行级锁,因此也就不存在表锁与行锁的冲突问题,自然也不需要意向锁这种多粒度协调机制。其优点是实现简单,开销小;缺点是写操作的并发性能极差,一个写操作会阻塞所有其他读写操作。而 InnoDB 通过引入行级锁和意向锁,实现了更细粒度的并发控制,大大提高了混合读写场景下的性能。
  3. 追问:SELECT ... FOR UPDATE 在加锁时,如果 WHERE 条件中的列没有索引,会发生什么?

    • 回答要点:
      • 锁升级:如果 WHERE 条件没有走索引,MySQL 将无法定位到具体的行,只能进行全表扫描。
      • 加锁范围:为了保证数据一致性,InnoDB 会对扫描过程中遇到的所有行都加上行级 X 锁。
      • 表锁效应:虽然加的是行锁,但由于每一行都被锁定了,其效果等同于加了表锁,会严重阻塞其他所有对该表的并发操作。
      • 意向锁:在这个过程中,事务依然会先获取表级别的 IX 锁,然后再对每一行加 X 锁。这个行为本身没有变,但锁定的范围从“精准的几行”扩大到了“全表所有行”。这是开发中必须避免的严重性能问题。