意向锁: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 锁意向锁的兼容性
意向锁是表级别的锁,它们之间的兼容性:
| 锁类型 | IS | IX | S | X |
|---|---|---|---|---|
| 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) │ │
│ │ 实际锁定具体的行 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 获取行锁前,必须先获取意向锁 │
│ 获取表锁时,通过意向锁判断是否有行锁冲突 │
│ │
└─────────────────────────────────────────────────────────────────┘锁的完整兼容性矩阵
综合行锁、意向锁、表锁的完整兼容性:
| 锁类型 | IS | IX | S | X |
|---|---|---|---|---|
| 行级 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 的锁协调员:在表级别声明行锁的存在,让表锁和行锁能够和谐共处。有了意向锁,加表锁时不需要遍历整张表,只需检查表上有没有意向锁即可。
