一 可重复读和可已提交的隔离级别区别

可重复读最主要的是解决了幻读的问题,幻读的解决是使用的GAP锁

二 锁分析的主要内容

2.1 使用主键进行等值查询

(1)使用SELECT … LOCK IN SHARE MODE来为记录加锁

SELECT * FROM hero WHERE number = 8 LOCK IN SHARE MODE;

主键具有唯一性质,所以不存在幻读的问题,所以只需要添加一个行锁就行
如果需要查询主键不存在的记录,比如说

SELECT * FROM hero WHERE number = 8 LOCK IN SHARE MODE;

由于number值为7的记录不存在,为了禁止幻读现象,所以需要在number值为8的记录加一个gap锁,也就是不允许别的事物插入number值在(3,8)在这个区间的新记录

mysql可重复读示例sql mysql可重复读死锁_mysql可重复读示例sql


如果在读未提交/读已提交的隔离级别下查询了一个主键不存在的记录,那么什么锁不需要加,因为在READ UNCOMMIT/READ COMMIT隔离级别下,并不需要幻读问题

2.2 使用主键进行范围查询

(1)使用SELECT … LOCK IN SHARE MODE语句来为记录加锁

SELECT * FROM hero WHERE number >= 8 LOCK IN SHARE MODE;

(1)为number值为8的聚族索引记录加一个S型记录锁

(2)为number值大于8的所有聚簇索引记录都加一个S型next-key锁(包括Supremum)

mysql可重复读示例sql mysql可重复读死锁_mysql可重复读示例sql_02

SELECT * FROM hero WHERE number <= 8 LOCK IN SHARE MODE;

在可重复读的隔离级别下,会为1,3,8,15这四条记录都加上S型的next-key锁,但是在判断了number值为15的记录不满足边界条件的时候,并不会释放加在这个记录上的锁

mysql可重复读示例sql mysql可重复读死锁_加锁_03


这样如果别的事务想要插入的新记录的number值在(-∞, 1)、(1, 3)、(3, 8)、(8, 15)之间的话,是会进入等待状态的。

(2)使用SELECT … FOR UPDATE语句来为记录加锁

和SELECT … LOCK IN SHARE MODE语句类似,加锁类型变成了X型

(3)使用UPDATE为记录加锁

1.没有更新二级索引,和SELECT…FOR UPDATE一样

2.如果更新了二级索引

UPDATE hero SET name = 'cao曹操' WHERE number >= 8;

因为要更新二级索引idx_name,所以也会对number值为8,15,20的聚族索引对应的idx_name二级索引加上X型锁

mysql可重复读示例sql mysql可重复读死锁_二级索引_04

UPDATE hero SET name = 'cao曹操' WHERE number <= 8;

这个语句会对number值为1,3,8,15的聚族索引加上X型的next-key Lock锁

,其中number值为15的聚族索引不满足条件,但是不会释放这个锁,但是也不会对它对应的二级索引加锁

mysql可重复读示例sql mysql可重复读死锁_mysql可重复读示例sql_05


(4)使用DETELE…来为记录加锁

和UPDATE一致

2.3 使用唯一二级索引进行等值查询

(1)使用SELECT … LOCK IN SHARE MODE语句来为记录加锁

SELECT * FROM hero WHERE name = 'c曹操' LOCK IN SHARE MODE;

因为唯一索引有唯一性质,所以不需要解决幻读问题,只需要在二级索引和一级索引上加锁X排他锁就可以

mysql可重复读示例sql mysql可重复读死锁_隔离级别_06


如果等值查询进行的查询记录不存在,那么需要加上对应的GAP锁

SELECT * FROM hero WHERE name = 'g关羽' LOCK IN SHARE MODE;

mysql可重复读示例sql mysql可重复读死锁_加锁_07


(2)使用SELECT … FOR UPDATE语句来为记录加锁

和SELECT … LOCK IN SHARE MODE语句类似,只不过加的是X型

(3)使用UPDATE …来为记录加锁

与SELECT … FOR UPDATE的加锁情况类似,不过如果被更新的列中还有别的二级索引列的话,这些对应的二级索引记录也会被加X型锁。

(4)使用DELETE…

与SELECT … FOR UPDATE的加锁情况类似,不过如果表中还有别的二级索引列的话,这些对应的二级索引记录也会被加X型锁。

2.4 使用唯一二级索引进行范围查询

SELECT … LOCK IN SHARE MODE语句来为记录加锁

SELECT * FROM hero FORCE INDEX(uk_name) WHERE name >= 'c曹操' LOCK IN SHARE MODE;

1.首先到name >= 'c曹操’的第一条记录

2.给二级索引和聚族索引加上间隙锁和行锁

3.判断下一个记录是否符合要求,重复1和2

mysql可重复读示例sql mysql可重复读死锁_二级索引_08

SELECT * FROM hero WHERE name <= 'c曹操' LOCK IN SHARE MODE;

1.首先为name值为’C曹操’的二级索引加上S型的next-key锁以及它对应的聚族索引记录加上S型锁,。然后还要给name值为’l刘备’的二级索引记录加S型next-key锁,name值为’l刘备’的二级索引记录不满足索引条件下推的name <= 'c曹操’条件,压根儿不会释放掉该记录的锁就直接报告server层查询完毕了。这样可以禁止其他事务插入name值在(‘c曹操’, ‘l刘备’)之间的新记录,从而防止幻读产生。所以这个过程的加锁示意图如下:

mysql可重复读示例sql mysql可重复读死锁_二级索引_09


这里大家要注意一下,设计InnoDB的大叔在这里给name值为’l刘备’的二级索引记录加的是S型next-key锁,而不是简单的gap锁。

使用SELECT … FOR UPDATE语句来为记录加锁
和SELECT … LOCK IN SHARE MODE语句类似,只不过加的是X型正经记录锁。
使用UPDATE …来为记录加锁

UPDATE hero SET country = '汉' WHERE name >= 'c曹操';

它的加锁方式和上边所说的SELECT … FOR UPDATE语句一致。如果有其他二级索引列也被更新,那么也会为这些二级索引记录进行加锁

UPDATE hero SET country = '汉' WHERE name <= 'c曹操';

我们前边说的索引条件下推这个特性只适用于SELECT语句,也就是说UPDATE语句中无法使用,无法使用索引条件下推这个特性时需要先进行回表操作,那么这个语句就会为name值为’c曹操’和’l刘备’的二级索引记录加X型next-key锁,对它们对应的聚簇索引记录进行加X型正经记录锁。不过之后在判断边界条件时,虽然name值为’l刘备’的二级索引记录不符合name <= 'c曹操’的边界条件,但是在REPEATABLE READ隔离级别下并不会释放该记录上加的锁,整个过程的加锁示意图就是:

mysql可重复读示例sql mysql可重复读死锁_隔离级别_10


使用DELETE …来为记录加锁

和UPDATE一致

2.5 使用普通二级索引进行等值查询

使用SELECT … LOCK IN SHARE MODE语句来为记录加锁

SELECT * FROM hero WHERE name = 'c曹操' LOCK IN SHARE MODE;

由于普通的二级索引没有唯一性,所以一个事务在执行上述语句之后,要阻止别的事务插入name值为’c曹操’的新记录

  • 对所有name值为’c曹操’的二级索引记录加S型next-key锁,它们对应的聚簇索引记录加S型正经就锁。
  • 对最后一个name值为’c曹操’的二级索引记录的下一条二级索引记录加gap锁。
  • mysql可重复读示例sql mysql可重复读死锁_二级索引_11

  • 如果对普通二级索引等值查询的值并不存在,加锁方式和唯一二级索引一致
    使用SELECT … FOR UPDATE语句来为记录加锁
    和SELECT … LOCK IN SHARE MODE语句类似,只不过加的是X型锁。
    使用UPDATE …来为记录加锁
    与SELECT … FOR UPDATE的加锁情况类似,不过如果被更新的列中还有别的二级索引列的话,这些对应的二级索引记录也会被加锁。
    使用DELETE …来为记录加锁
    与SELECT … FOR UPDATE的加锁情况类似,不过如果表中还有别的二级索引列的话,这些对应的二级索引记录也会被加锁。

2.6 使用普通二级索引进行范围查询

与唯一二级索引的加锁情况类似

2.7 全表扫描

SELECT * FROM hero WHERE country  = '魏' LOCK IN SHARE MODE;

由于country列上未建索引,所以只能采用全表扫描的方式来执行这条查询语句,存储引擎每读取一条聚簇索引记录,就会为这条记录加锁一个S型next-key锁,然后返回给server层,如果server层判断country = '魏’这个条件是否成立,如果成立则将其发送给客户端,否则会向InnoDB存储引擎发送释放掉该记录上的锁的消息,不过在REPEATABLE READ隔离级别下,InnoDB存储引擎并不会真正的释放掉锁,所以聚簇索引的全部记录都会被加锁,并且在事务提交前不释放。

mysql可重复读示例sql mysql可重复读死锁_二级索引_12


全部记录都被加了next-key锁!此时别的事务别说想向表中插入啥新记录了,就是对某条记录加X锁都不可以,这种情况下会极大影响访问该表的并发事务处理能力,所以如果可能的话,尽可能为表建立合适的索引吧~