快手二面 MySQL的RC和RR有什么区别?
底层实现
- RC(read commit)底层实现:MVCC版本管理
- RR(repeat read) 底层实现:MVCC版本管理和间隙锁
PS: 写在前面,如果对你有用的话,一定不要忘记送个花花呀,这么高质量又免费的帖子很少了
MVCC实现上的区别
- 事务开始时生成一个快照,有一个版本号(事务ID,transaction id)
- 修改时基于当前读(读取最新的数据)把修改后的数据和版本号追加到undo-log里
- RC级别下:每一个语句执行前都会重新算出一个新的视图(版本号变成新的)| 查询只承认在语句启动前就已经提交完成的数据
- >= 当前版本号且已经提交的事务版本号,如果没有,就不变
- RR级别下:只有在事务开始时才生成快照 | 查询只承认在事务启动前就已经提交完成的数据
- 查询时基于快照读,在undo-log里从后往前查询,查询第一个 <= 当前事务版本号的数据,这样可以保证就读不到其它数据未提交的数据,解决了脏读
- RC级别下:因为每次读都会生成新的快照(版本号),所以下次查询时的版本号不一样,读取到的数据可能不一样,可能存在不可重复读问题
- RR级别下:每次读的版本号是一样的,所以每次读的数据都是一样的,是可重复读的
RR独有的间隙锁
RR隔离级别下使用了间隙锁来应对幻读的问题
什么是间隙锁?更新时不但锁住某一行的数据,也锁住他的间隙,比如范围查询,不但锁住范围内的行,把这个范围也锁住,这个时候不允许在这个区间插入数据,可以一定程度避免幻读
- 为什么说是一定程度:因为只有在修改时才加锁,事务1当第一次查询到某个范围,得到结果集1,事务2这个时候在这个范围集里面插入了新数据,并且提交了事务释放锁,事务1第二次查询(当前读)就发现结果集多了事务2插入的新数据,基于此结果集的更新(因为是当前读)就会多出一些数据。
- 间隙锁加锁规则:
- 原则 1:加锁的基本单位是 next-key lock。next-key lock 是前开后闭区间。
- 原则 2:查找过程中访问到的对象才会加锁。
- 优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。因为唯一索引只能存在一行数据,不需要使用间隙锁
- 优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。
- 一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。
MySQL为什么选择RR为默认隔离级别
主要原因是因为:RC级别下不能保证binlog记录顺序,主从同步时会有问题。
MySQL在主从复制的过程中,数据的同步是通过bin log进行的,简单理解就是主服务器把数据变更记录到bin log中,然后再把bin log同步传输给从服务器,从服务器接收到bin log之后,再把其中的数据恢复到自己的数据库存储中。
例如,有一个数据库表t1,表中有如下两条记录:
CREATE TABLE `t1` ( `a` int(11) DEFAULT NULL, `b` int(11) DEFAULT NULL, KEY `a` (`a`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; insert into t1 values(10,2),(20,1);
接着开始执行两个事务的写操作:
以上两个事务执行之后,数据库里面的记录会变成(11,2)和(20,2),这个发上在主库的数据变更大家都能理解。
因为事务的隔离级别是read committed,所以,事务1在更新时,只会对b=2这行加上行级锁,不会影响到事务2对b=1这行的写操作。
以上两个事务执行之后,会在bin log中记录两条记录,因为事务2先提交,所以UPDATE t1 SET b=2 where b=1;
会被优先记录,然后再记录UPDATE t1 SET a=11 where b=2;
这样bin log同步到备库之后,SQL语句回放时,会先执行UPDATE t1 SET b=2 where b=1;
,再执行UPDATE t1 SET a=11 where b=2;
。
这时候,数据库中的数据就会变成(11,2)和(11,2)。这就导致主库和备库的数据不一致了!!!
为了避免这样的问题发生。MySQL就把数据库的默认隔离级别设置成了RR。因为RR隔离级别下有间隙锁,可以控制事务2在事务1之后执行。
最后,如果对你有用的话,一定不要忘记送个花花呀,这么高质量又免费的帖子很少了