一、当前读和快照读:
1.、快照读(snapshot read):
简单的select操作(不包括 select ... lock in share mode, select ... for update)
2、当前读(current read):
select ... lock in share mode
select ... for update
insert
update
delete
3、快照读和当前读的实现区别:
在RR级别下,快照读是通过MVVC(多版本控制)和undo log来实现的,当前读是通过加record lock(记录锁)和gap lock(间隙锁)来实现的。
二、幻读:
1、定义:
指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行。
2、注意:
<1>、在可重复读隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的。因此,幻读在“当前读”下才会出现。
<2>、幻读仅专指“新插入的行”。
3、幻读出现导致的问题:
对于下表:
CREATE TABLE `t` (
`id` int(11) NOT NULL,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c` (`c`)
) ENGINE=InnoDB;
insert into t values(0,0,0),(5,5,5),
(10,10,10),(15,15,15),(20,20,20),(25,25,25);
操作如下:
<1>、语义上的问题。
语句Q1会命中d=5的这一行,对应的主键id=5,因此在select 语句执行完成后,id=5这一行会加一个写锁,而且由于两阶段锁协议,这个写锁会在执行commit语句的时候释放。
session B的第二条语句update t set c=5 where id=0,语义是“我把id=0、d=5这一行的c值,改成了5”。由于在T1时刻,session A 还只是给id=5这一行加了行锁, 并没有给id=0这行加上锁。因此,session B在T2时刻,是可以执行这两条update语句的。这样,就破坏了 session A 里Q1语句要锁住所有d=5的行的加锁声明。
session C也是一样的道理,对id=1这一行的修改,也是破坏了Q1的加锁声明。
<2>、数据一致性的问题。
分析上面对表的操作,
①、经过T1时刻,id=5这一行变成 (5,5,100),当然这个结果最终是在T6时刻正式提交的;
②、经过T2时刻,id=0这一行变成(0,5,5);
③、经过T4时刻,表里面多了一行(1,5,5);
④、其他行跟这个执行序列无关,保持不变。
而此时binlog里面的内容为:
/*T2时刻,session B事务提交,写入了两条语句*/
update t set d=5 where id=0; /*(0,0,5)*/
update t set c=5 where id=0; /*(0,5,5)*/
/*T4时刻,session C事务提交,写入了两条语句;*/
insert into t values(1,1,5); /*(1,1,5)*/
update t set c=5 where id=1; /*(1,5,5)*/
/*T6时刻,session A事务提交,写入了update t set d=100 where d=5 这条语句。*/
update t set d=100 where d=5;/*所有d=5的行,d改成100*/
由此可以看出,这个语句序列,不论是拿到备库去执行,还是以后用binlog来克隆一个库,这三行的结果,都变成了 (0,5,100)、(1,5,100)和(5,5,100)。也就是说,id=0和id=1这两行,发生了数据不一致。
注意:即使session A把所有的行都加了写锁,session B在执行第一个update语句的时候就被锁住了。需要等到T6时刻session A提交以后,session B才能继续执行。所以对于id=0这一行,最终结果是 (0,5,5)。但对于id=1这一行,在T1时刻,给所有行加锁的时候,id=1这一行还不存在,不存在也就加不上锁。所以id=1这一行,在数据库里面的结果是(1,5,5)。
三、间隙锁(Gap Lock、幻读的解决办法):
1、概念:
锁的就是两个值之间的空隙。
2、行锁之间的冲突关系:
<1>、读锁和写锁的冲突关系:
<2>、跟间隙锁存在冲突关系的,是“往这个间隙中插入一个记录”这个操作。间隙锁之间都不存在冲突关系。
3、间隙锁和行锁合称next-key lock,每个next-key lock是前开后闭区间。
比如说,表t初始化以后,如果用select * from t for update要把整个表所有记录锁起来,就形成了7个next-key lock,分别是 (-∞,0]、(0,5]、(5,10]、(10,15]、(15,20]、(20, 25]、(25, +suprenum]。
注意:①、把间隙锁记为开区间,把next-key lock记为前开后闭区间。
②、对于“suprenum”,在实现上,InnoDB给每个索引加了一个不存在的最大值suprenum,这样才符合前面说的“都是前开后闭区间”。