bigywater.tech
MySQL / InnoDB · 52

Gap Lock

给一个真实存在的id范围加FOR UPDATE,再往三个不同位置插入数据,用performance_schema.data_locks确认间隙锁到底锁住了哪几段。

2个间隙
BETWEEN 10 AND 30真实锁住的间隙数量
id=5/35立即成功
锁范围之外的插入真实完全不受影响

做的事:给一个真实存在的id范围加FOR UPDATE,再往各个间隙里插数据

accounts表的id取值是10,20,30,...,80,中间天然留着一个个间隙。

session ASTART TRANSACTION; SELECT * FROM accounts WHERE id BETWEEN 10 AND 30 FOR UPDATE; SELECT SLEEP(6); COMMIT;

验证:真实的加锁范围,用performance_schema.data_locks直接看

实测 · 本机performance_schema.data_locks真实输出
lock_type  lock_mode        lock_data
RECORD     X                20   ← next-key锁:真实覆盖(10,20)这个间隙 + 记录20本身
RECORD     X                30   ← next-key锁:真实覆盖(20,30)这个间隙 + 记录30本身
RECORD     X,REC_NOT_GAP    10   ← 纯记录锁:真实没有锁10前面的间隙

这条BETWEEN 10 AND 30真实只产生了3条锁记录,而不是给整个索引都加锁:id=10上真实标了REC_NOT_GAP,说明它前面的间隙没被锁;id=20和id=30上真实是普通的X锁(不带REC_NOT_GAP,也不带纯GAP标记),这种"记录锁+它前面的间隙锁"合在一起的锁,就是Next-Key Lock,下一篇会专门拆开看。

验证:真实往三个不同位置插入,只有间隙内部真实被挡住

实测 · 本机执行输出
插入 id=15(在(10,20)间隙内部):    ERROR 1205 Lock wait timeout   ← 真实被挡住
插入 id=5(在10前面,锁没覆盖到):   成功,真实立即完成
插入 id=35(在30后面,锁没覆盖到):   成功,真实立即完成
间隙锁真实锁的是"查询语句真实扫描到的那部分间隙",不是整个索引的所有间隙。这条BETWEEN 10 AND 30的扫描真实从id=10开始,往后经过id=20、id=30,所以真实锁住了(10,20)和(20,30)这两段;id=10前面的间隙,扫描根本没有真实经过(这条查询压根不关心比10还小的id),所以没有被锁;id=30后面同理——扫描到30就真实满足了上界条件,不需要再往后走,(30,40)这段间隙也就没有被锁。这也是为什么本文能在id=5和id=35这两个位置真实立即插入成功,只有真正落在(10,20)间隙内部的id=15会被间隙锁真实挡住。

分步演示:三个插入位置,只有一个真实被间隙锁挡住

每一步都是本文上面用真实并发会话跑出来的原始结果。

间隙锁只锁真实扫描到的那几段
这一步新验证的插入位置

    本文用到的工具

    • 本机 MySQL 26.7.0(InnoDB),accounts表(id取值10,20,...,80,天然留有间隙)
    • performance_schema.data_locks:真实查看锁的具体范围和类型
    京ICP备2026067502号-1京公网安备11010502063487号