一 可重复读和可已提交的隔离级别区别
可重复读最主要的是解决了幻读的问题,幻读的解决是使用的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)在这个区间的新记录
如果在读未提交/读已提交的隔离级别下查询了一个主键不存在的记录,那么什么锁不需要加,因为在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)
SELECT * FROM hero WHERE number <= 8 LOCK IN SHARE MODE;
在可重复读的隔离级别下,会为1,3,8,15这四条记录都加上S型的next-key锁,但是在判断了number值为15的记录不满足边界条件的时候,并不会释放加在这个记录上的锁
这样如果别的事务想要插入的新记录的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型锁
UPDATE hero SET name = 'cao曹操' WHERE number <= 8;
这个语句会对number值为1,3,8,15的聚族索引加上X型的next-key Lock锁
,其中number值为15的聚族索引不满足条件,但是不会释放这个锁,但是也不会对它对应的二级索引加锁
(4)使用DETELE…来为记录加锁
和UPDATE一致
2.3 使用唯一二级索引进行等值查询
(1)使用SELECT … LOCK IN SHARE MODE语句来为记录加锁
SELECT * FROM hero WHERE name = 'c曹操' LOCK IN SHARE MODE;
因为唯一索引有唯一性质,所以不需要解决幻读问题,只需要在二级索引和一级索引上加锁X排他锁就可以
如果等值查询进行的查询记录不存在,那么需要加上对应的GAP锁
SELECT * FROM hero WHERE name = 'g关羽' LOCK IN SHARE MODE;
(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
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刘备’)之间的新记录,从而防止幻读产生。所以这个过程的加锁示意图如下:
这里大家要注意一下,设计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隔离级别下并不会释放该记录上加的锁,整个过程的加锁示意图就是:
使用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锁。
如果对普通二级索引等值查询的值并不存在,加锁方式和唯一二级索引一致
使用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存储引擎并不会真正的释放掉锁,所以聚簇索引的全部记录都会被加锁,并且在事务提交前不释放。
全部记录都被加了next-key锁!此时别的事务别说想向表中插入啥新记录了,就是对某条记录加X锁都不可以,这种情况下会极大影响访问该表的并发事务处理能力,所以如果可能的话,尽可能为表建立合适的索引吧~