行级锁补充【间隙锁,临键锁】
目录
前言
应用
1.索引上的等值查询(唯一索引->主键id),给不存在的记录加锁时,优化为间隙锁。
2.索引上的等值查询(普通索引),向右遍历时最后一个值不满足查询需求时,next-keylock退化为间隙锁。
3.索引上的范围查询(唯一索引)--会访问到不满足条件的第一个值为止。
前言
本篇博客,通过案例,详细介绍间隙锁和临键锁的使用
默认情况下,InnoDB在REPEATABLEREAD事务隔离级别运行,InnoDB使用next-key锁【临键锁】进行搜索和索引扫描,以防止幻读。
幻读:一般在进行插入操作时,插入前后,查询数据,导致整体不一致
举一个例子:在同一个事务中,两次执行相同的范围查询,第二次看到了第一次没看到的“新行”(别的事务插入的),就好像“幻觉”一样。
时间 | 事务 A(你) | 事务 B(别人) |
---|---|---|
T1 | BEGIN; | |
T2 | SELECT * FROM tb_user WHERE age > 20; → 查到 3 行 | |
T3 | INSERT INTO tb_user (age) VALUES (25); COMMIT; | |
T4 | SELECT * FROM tb_user WHERE age > 20; → 查到 4 行(多了一行) |
REPEATABLEREAD事务隔离级别:中文翻译为:可重复读, 解决 脏读,不可重复读问题
同时 通过 锁【间隙锁,临键锁】,解决幻读
总的来说,由于mysql默认的隔离级别是可重重读,并且在使用锁的条件下,脏读,幻读,不可重复读,等问题都可以被解决
应用
1.索引上的等值查询(唯一索引->主键id),给不存在的记录加锁时,优化为间隙锁。
翻译就是:如果操作当前表中,不存在的数据,此时的表会转成间隙锁
举一个例子,tb_user表中id是1,2,6 这样的,2-6之前没有数据的,如果这时候更新id=3的数据,由于id=3所在数据不存在,会将,id=3前一行记录和后一行记录,范围,加上间隙锁
测试:向11<id<20 范围中,插入一条数据,观察看是否会被阻塞

2.索引上的等值查询(普通索引),向右遍历时最后一个值不满足查询需求时,next-keylock退化为间隙锁。
翻译就是:如果查询条件是没有添加索引的字段,那么锁的范围将从原本的行级锁转变为表级锁/全表的间隙锁
测试:如果,我修改id=20,将name 值 修改为王凌,观察右侧,是否会发生阻塞
测试:向tb_user表中插入新的数据
3.索引上的范围查询(唯一索引)--会访问到不满足条件的第一个值为止。
测试:向tb_user表添加一条新的数据
分析
SELECT * FROM tb_user WHERE id > 12 LOCK IN SHARE MODE;
这条语句在 默认隔离级别(Repeatable Read) 下,会:
-
使用主键索引范围扫描(因为
id
是主键)。 -
对 id > 12 的所有记录 加 共享记录锁(S 锁)。
-
对 最后一个记录之后的间隙 加 间隙锁(gap lock)。
-
综合起来就是 next-key lock(临键锁),锁住的是:
-
记录本身(如 id = 15、20)
-
以及它们之间的间隙(如 (12, 15)、(15, 20)、(20, +∞))
-
所以:
-
你插入的
id = 22
落在 (20, +∞) 的间隙中,这个间隙被 gap lock 锁住。 -
插入操作需要在这个间隙中加 插入意向锁(insert intention lock),但被已有的 gap lock 阻塞。
-
最终超时,抛出:ERROR 1205 (HY000): Lock wait timeout exceeded
测试:在不被锁,锁包含的范围,中修改数据,看是否还会被锁