mysql在RR模式下,可以通过加临键锁来防止出现幻读,这里说下对幻读的理解,基本主要是针对当前读而言,对于快照读来说,读的本来就是快照视图内的已提交事务,即便是另一个事务插入数据,也不会读到。在当前读的情况下,如update或for update之类的操作方式中,会使用当前数据,这样才会读到其它事务还未提交的数据。
临键锁加锁测试
临键锁按定义来说,就是为了解决幻读而设计的,那么它要达到的目的就是每次查询都是相同的数据,这就说明在加锁期间,其它事务不能插入可能干扰查询结果的数据项。
查询项为索引的情况
如果查询项为索引项,会对查询行以及左右索引间隙进行加锁,这样就不会插入干扰查询项的数据了。如果这个索引为唯一索引,就可以退化为行锁,因为加行锁就可以避免幻读了。
等于查询
客户端A先开启事务,然后修改对应的数据,按理论推断,修改student_age为10的一行,会锁定这行,以及两边的间隙,即(-无穷大,10]和[10,12),下面用另一个客户端进行插入试试。
客户端A执行下面的命令:
START TRANSACTION;
update students set student_name = "ff" where student_age = 10;
-
客户端B插入student_age为11的记录,应该会被阻塞,这里提示unkown error,应该就是间隙锁导致的。
image.png -
客户端B插入student_age为8的记录,应该也会被阻塞。通过看锁的情况,当前确实存在Gap锁给锁住了,不过具体字段没看太明白。
image.png -
客户端B插入student_age为12的记录,可以插入,说明间隙锁右边是开空间的。
image.png
范围查询
客户端A先开启事务,然后修改对应的数据,按理论推断,锁附近的间隙,修改student_age >=16的行,会锁定这行,以及当中和后面的间隙,即(14,18]和[18, +无穷大),下面用另一个客户端进行插入试试。
客户端A执行下面的命令:
START TRANSACTION;
update students set student_name = "ff" where student_age >= 16;
-
客户端B插入student_age为15的值,应该会被阻塞,可见这个gap锁实现依赖索引项,16本身在已有索引项14和18之间,所以锁住的是14到18之间。
image.png -
客户端B插入student_age为20的值,也会被阻塞。
image.png 客户端B插入student_age为8的值,不在范围内,应该可以正常插入,但事实上没成功,这个后面查到是之前的事务没提交,导致有很多锁,重启服务端以后就可以了。
客户端B插入student_age为13的值,不在范围内,可以正常插入
查询项无索引的情况下
无索引的情况下间隙锁会锁全表,因为没有索引项可以确定要锁哪些范围,只能锁全表。
客户端A执行下面的命令:
start TRANSACTION;
update students set student_age = 9 where student_name = "bb";
客户端B无论插入那种student_name,都会被锁,说明全表被锁,A提交后,B再插入就可以成功,由此说明我们的推断没问题。