间隙锁仅生效于可重复读隔离级别,是临键锁的组成部分,用于防止幻读。它仅在范围查询使用索引时触发,锁定索引记录间的间隙,阻止其他事务插入新行。可通过性能模式或观察插入阻塞来验证其存在。
直接给出结论:Gap Lock并非在所有场景下都能使用,它对运行环境有严格要求——必须处于REPEATABLE READ隔离级别,否则根本没有触发的机会。更准确地说,它从不单独发挥作用,而是作为Next-Key Lock的组成部分,专门针对“不存在的值”进行封锁,最终目标是防止幻读。此外,只有在范围查询且走索引时,Gap Lock才会真正生效。
下面详细拆解几个关键点。
长期稳定更新的攒劲资源: >>>点此立即查看<<<
这是第一道硬性门槛。简单来说,如果隔离级别设置为READ COMMITTED,即使执行SELECT * FROM t WHERE id > 10 FOR UPDATE,InnoDB也只会对已存在的数据行加Record Lock,行与行之间的间隙不会上锁。后果是幻读风险直接暴露:其他事务可以随时在id=15的位置插入新记录。
因此,操作前务必确认当前会话隔离级别为REPEATABLE READ。检查方法很简单:运行SELECT @@transaction_isolation;,如果返回REPEATABLE-READ,前提条件才成立。否则,Gap Lock不可用。
在InnoDB中,不会出现“仅加Gap Lock”的操作。Gap Lock始终与Record Lock打包,形成完整的Next-Key Lock。举个例子:假设表中有id=5、10、20三行数据,执行SELECT * FROM t WHERE id > 5 FOR UPDATE,InnoDB实际会添加两个Next-Key Lock:(5,10](锁记录10及间隙(5,10))和(10,20](锁记录20及间隙(10,20)),以及一个(20,+supremum]。注意括号中的开区间才是Gap Lock真正发挥作用的区域——它的职责是阻止其他事务申请Insert Intention Lock。任何插入操作必须先获取该意向锁才能继续,Gap Lock拦截的就是这一步。
这或许有些反直觉,但正是InnoDB的设计哲学:它锁的不是“值”,而是“位置”。
以下是几个常见但容易踩坑的场景:
WHERE status = 'pending':如果status字段没有建立索引,InnoDB会进行全表扫描,间隙锁完全不参与。即使有索引,等值查询搭配非唯一索引时,仍可能只加Record Lock,不覆盖前后间隙。WHERE id > 10(id为主键):属于标准的走索引范围查询,条件满足,Next-Key Lock生效,锁住(10,+supremum]整个后缀区间。SELECT * FROM t FOR UPDATE(无WHERE条件):这是最极端的情况——全表扫描时,InnoDB会锁住从(∞, min_key]到(max_key, +supremum]的全部间隙,逻辑上相当于锁住整张表。虽然操作本身允许,但并发性能会急剧下降。仅仅设置SET TRANSACTION ISOLATION LEVEL REPEATABLE READ还不够,需要观察锁的实际行为。最可靠的验证方式是通过performance_schema.data_locks查询:SELECT LOCK_MODE, LOCK_DATA FROM performance_schema.data_locks WHERE LOCK_TRX_ID = '你的事务ID';。如果LOCK_MODE显示为RECORD & GAP或RECORD & NEXT-KEY,说明Gap部分已生效;如果只显示RECORD,则仅为行锁;如果查不到记录,很可能没有加锁。
还有一种更直观的验证方法:事务A执行SELECT * FROM t WHERE id > 5 FOR UPDATE后,让事务B立即执行INSERT INTO t VALUES (6, ...)。如果被阻塞,说明间隙确实被锁住了。
Gap Lock真正棘手的地方在于,它锁的是“不存在的值”——多个事务对同一段空隙加锁时,彼此之间互不可见。正因为看不到,一旦插入顺序不同,就可能引发死锁。更隐蔽的一点是,它只对当前读(FOR UPDATE或LOCK IN SHARE MODE)有效;普通SELECT走的是MVCC快照读,根本感知不到间隙锁的存在。换句话说,快照读查询到的数据看起来没问题,但其他事务可能已经向空隙中插入了数据。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述