为什么间隙锁与间隙锁之间是兼容的?而有的next-keylock和next-keylock是不兼容的呢?
在 InnoDB 中,间隙锁(Gap Lock) 和 Next-Key Lock 的兼容性规则不同,核心原因在于它们的 锁类型(S/X) 和 锁定范围 的差异。以下是详细解释:
- 间隙锁(Gap Lock)的兼容性
- 锁定对象:仅锁定索引记录之间的间隙(例如 (10, 20)),不锁定任何实际存在的记录。
- 锁类型:
- 间隙锁 没有共享锁(S)和排他锁(X)的区分,所有间隙锁本质上都是 “共享” 的,因为它们仅用于阻止其他事务在间隙内插入新数据。
- 兼容性规则:
- 不同事务对 同一间隙 加间隙锁是兼容的,不会互相阻塞。因为间隙锁的目标是防止插入操作,而多个事务的间隙锁并不冲突。
- 示例:
-- 事务 A BEGIN; SELECT * FROM t WHERE id = 15 FOR UPDATE; -- 锁定间隙 (10, 20) -- 事务 B BEGIN; SELECT * FROM t WHERE id = 18 FOR UPDATE; -- 同样锁定间隙 (10, 20) -- 两个事务的间隙锁兼容,事务 B 不会被阻塞!
- Next-Key Lock 的兼容性
-
锁定对象:Next-Key Lock = 记录锁(Record Lock) + 间隙锁(Gap Lock),锁定一个 左开右闭区间(例如 (10, 20])。
-
锁类型:
- Next-Key Lock 会根据操作类型加 共享锁(S) 或 排他锁(X)。例如:
- SELECT ... FOR UPDATE 会加 X 型 Next-Key Lock。
- SELECT ... LOCK IN SHARE MODE 会加 S 型 Next-Key Lock。
-
兼容性规则:
- X 型 Next-Key Lock 之间互斥:两个事务对同一范围加 X 型 Next-Key Lock 会互相阻塞。
- S 型 Next-Key Lock 之间兼容:允许其他事务对同一范围加 S 型锁。
- X 型与 S 型 Next-Key Lock 互斥:X 型锁会阻塞 S 型锁,反之亦然。
-
示例:
-- 事务 A BEGIN; SELECT * FROM t WHERE id = 20 FOR UPDATE; -- 加 X 型 Next-Key Lock (10, 20] -- 事务 B BEGIN; SELECT * FROM t WHERE id = 20 FOR UPDATE; -- 尝试加 X 型 Next-Key Lock (10, 20] -- 事务 B 被阻塞,直到事务 A 提交或回滚!
- 为什么间隙锁之间兼容,而 X 型 Next-Key Lock 之间不兼容?
维度 | 间隙锁(Gap Lock) | X 型 Next-Key Lock |
---|---|---|
锁定目标 | 仅间隙(阻止插入) | 间隙 + 记录(阻止插入和修改/删除) |
锁类型 | 无 S/X 区分(本质是共享的) | 排他锁(X Lock) |
冲突场景 | 仅与插入操作冲突 | 与插入、修改、删除操作均冲突 |
设计目的 | 防止幻读(仅针对插入) | 防止幻读 + 保证当前读的一致性 |
关键区别:
间隙锁只关注“间隙内的插入操作”,而 Next-Key Lock 不仅要防止插入,还要锁定记录本身(通过记录锁),因此后者需要更强的排他性。
- 锁兼容性矩阵
当前锁类型 | 请求的锁类型 | 间隙锁(Gap Lock) | X 型 Next-Key Lock | S 型 Next-Key Lock |
---|---|---|---|---|
间隙锁 | 间隙锁 | ✅ 兼容 | ✅ 兼容 | ✅ 兼容 |
X 型 Next-Key Lock | X 型 Next-Key Lock | ❌ 不兼容 | ❌ 不兼容 | ❌ 不兼容 |
S 型 Next-Key Lock | S 型 Next-Key Lock | ✅ 兼容 | ✅ 兼容 | ✅ 兼容 |
总结
- 间隙锁之间兼容:因为它们只阻止插入操作,且不区分 S/X 类型,多个事务可以同时持有同一间隙的锁。
- X 型 Next-Key Lock 之间不兼容:因为它们既锁定间隙(防止插入)又锁定记录(防止修改/删除),且 X 锁是排他的
记录锁是不是完全是X型的?
答案是不是的
在 InnoDB 中,记录锁(Record Lock) 并非完全是 X 型(排他锁)。记录锁的类型取决于具体操作的类型(读或写),因此可以是 S 型(共享锁) 或 X 型(排他锁)。
- 记录锁的类型
锁类型 | 触发操作 | 兼容性 |
---|---|---|
S 型(共享锁) | 显式加锁(如 SELECT ... LOCK IN SHARE MODE)或隐式加锁(如外键检查) | 允许其他事务加 S 型锁,但禁止加 X 型锁。 |
X 型(排他锁) | 显式加锁(如 SELECT ... FOR UPDATE)或隐式加锁(如 UPDATE、DELETE) | 禁止其他事务加 S/X 型锁。 |
- 不同操作触发的记录锁类型
(1) 显式加锁-
S 型锁:
SELECT * FROM t WHERE id = 1 LOCK IN SHARE MODE; -- 对 id=1 的记录加 S 锁
允许其他事务读取同一行,但禁止修改或加 X 锁。
-
X 型锁:
SELECT * FROM t WHERE id = 1 FOR UPDATE; -- 对 id=1 的记录加 X 锁
禁止其他事务读取(加 S 锁)或修改(加 X 锁)该行。
-
- (2) 隐式加锁
-
写操作(X 型锁):
UPDATE t SET name = 'Alice' WHERE id = 1; -- 隐式对 id=1 的记录加 X 锁 DELETE FROM t WHERE id = 1; -- 隐式加 X 锁
自动加 X 锁,确保数据修改的独占性。
-
读操作(S 型锁):
某些场景下,InnoDB 会自动加 S 锁,例如:-- 外键约束检查时,对父表记录加 S 锁 INSERT INTO child (parent_id) VALUES (1); -- 检查 parent_id=1 是否存在
-
- 锁的兼容性
当前锁类型 | 请求的锁类型 | 是否兼容 |
---|---|---|
S 锁 | S 锁 | ✅ 兼容 |
S 锁 | X 锁 | ❌ 不兼容 |
X 锁 | S 锁 | ❌ 不兼容 |
X 锁 | X 锁 | ❌ 不兼容 |
示例:
事务 A 对 id=1 加 S 锁后,事务 B 仍可加 S 锁,但不可加 X 锁。
事务 A 对 id=1 加 X 锁后,事务 B 的任何锁请求都会被阻塞。
- 记录锁的适用场景
- (1) S 型锁(共享锁)
- 需要读取数据并确保其他事务不会修改(但允许读)。
- 例如:在事务中读取某行数据后,需要基于该值进行后续逻辑,但允许其他事务并发读取。
- (2) X 型锁(排他锁)
- 需要独占修改或删除数据。
- 例如:库存扣减、订单状态更新等需要强一致性的场景。
- 实战示例
场景 1:S 锁与 S 锁兼容
-- 事务 A
BEGIN;
SELECT * FROM t WHERE id = 1 LOCK IN SHARE MODE; -- 加 S 锁
-- 事务 B
BEGIN;
SELECT * FROM t WHERE id = 1 LOCK IN SHARE MODE; -- 允许加 S 锁,事务 B 不阻塞
场景 2:S 锁与 X 锁冲突
-- 事务 A
BEGIN;
SELECT * FROM t WHERE id = 1 LOCK IN SHARE MODE; -- 加 S 锁
-- 事务 B
BEGIN;
SELECT * FROM t WHERE id = 1 FOR UPDATE; -- 尝试加 X 锁,事务 B 被阻塞!
总结
- 记录锁不完全是 X 型:根据操作类型,可以是 S 型或 X 型。
- S 型锁:用于共享读场景,允许并发读但禁止写。
- X 型锁:用于独占写场景,禁止其他事务的读写操作。
关键原则:
读操作(如 LOCK IN SHARE MODE)加 S 锁,写操作(如 FOR UPDATE、UPDATE)加 X 锁。
合理选择锁类型,平衡并发性能与数据一致性。