Skip to content

意向锁:InnoDB 的锁协调员

你已经知道了表锁和行锁,但你有没有想过这个问题:

InnoDB 的行锁是锁定索引记录的,那表锁要怎么判断某行有没有被锁呢?

总不能遍历整张表的所有行吧?

意向锁就是来解决这个问题的。


为什么要有意向锁?

想象一下没有意向锁的情况:

sql
-- 事务 A:给 id=1 的行加行锁
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
-- InnoDB 给 id=1 的行加了行锁

-- 事务 B:想给整张表加表锁
LOCK TABLES orders WRITE;
-- 事务 B 需要知道有没有行被锁了
-- 怎么办?扫描整张表的所有行?

显然,扫描整张表太慢了。

意向锁的本质:在表级别声明「我正在锁某些行」,让表锁知道行锁的存在。


意向锁的类型

意向共享锁(IS 锁)

事务想要获取表中某些行的共享锁。

sql
SELECT * FROM orders WHERE id = 1 LOCK IN SHARE MODE;
-- InnoDB 会自动给 orders 表加 IS 锁

意向排他锁(IX 锁)

事务想要获取表中某些行的排他锁。

sql
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
-- InnoDB 会自动给 orders 表加 IX 锁

UPDATE orders SET status = 'paid' WHERE id = 1;
-- InnoDB 会自动给 orders 表加 IX 锁

意向锁的兼容性

意向锁是表级别的锁,它们之间的兼容性:

锁类型ISIXSX
IS
IX
S
X

关键点

  • 意向锁之间互相兼容(IS 和 IX 可以同时存在)
  • 意向锁和表级锁 S 兼容
  • 意向锁和表级锁 X 互斥

意向锁的工作原理

sql
-- 事务 A:给 id=1 的行加行锁
BEGIN;
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
-- 自动发生:
-- 1. 给 id=1 的行加行级 X 锁
-- 2. 给 orders 表加表级 IX 锁

-- 事务 B:想给表加表级 X 锁
LOCK TABLES orders WRITE;
-- 检查表级锁:
-- 1. 发现表上有 IX 锁(说明有行锁存在)
-- 2. IX 和 X 互斥,等待行锁释放
┌─────────────────────────────────────────────────────────────────┐
│                    意向锁的层次结构                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  表级别:                                                        │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │ 意向锁(IS/IX)                                         │    │
│  │ 说明「表中某些行正在被锁定」                              │    │
│  └─────────────────────────────────────────────────────────┘    │
│                           ↑                                     │
│                           │                                    │
│  行级别:                                                        │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │ 行锁(S/X)                                             │    │
│  │ 实际锁定具体的行                                        │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                 │
│  获取行锁前,必须先获取意向锁                                   │
│  获取表锁时,通过意向锁判断是否有行锁冲突                        │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

锁的完整兼容性矩阵

综合行锁、意向锁、表锁的完整兼容性:

锁类型ISIXSX
行级 S--
行级 X--
IS
IX
表级 S
表级 X

意向锁的实际意义

场景一:表锁判断行锁

sql
-- 事务 A:锁定部分行
BEGIN;
SELECT * FROM orders WHERE status = 'pending' FOR UPDATE;
-- 自动加 IX 锁

-- 事务 B:想加表锁
LOCK TABLES orders WRITE;
-- 检查表上是否有锁:
-- - 发现了 IX 锁
-- - IX 和表级 X 锁互斥
-- - 等待行锁释放

场景二:批量锁的优化

sql
-- 事务 A:锁定多行
BEGIN;
SELECT * FROM orders WHERE user_id = 1 FOR UPDATE;
-- 给 user_id=1 的所有订单加行锁
-- 同时只需要给表加一个 IX 锁

-- 如果没有意向锁:
-- - 需要维护每个行锁的状态
-- - 表锁判断时需要遍历所有行
-- 意向锁让这一切变得高效

查看意向锁

sql
-- 查看 InnoDB 锁信息
SHOW ENGINE INNODB STATUS\G
-- 输出中可以看到:
-- RECORD LOCKS space id 123 page no 4 n bits 72 index PRIMARY of table `guide`.`orders`
-- lock_mode X locks rec but not gap
-- Record lock

-- 查看锁等待
SELECT * FROM information_schema.INNODB_LOCK_WAITS;

一句话总结

意向锁是 InnoDB 的锁协调员:在表级别声明行锁的存在,让表锁和行锁能够和谐共处。有了意向锁,加表锁时不需要遍历整张表,只需检查表上有没有意向锁即可。

基于 VitePress 构建