mysql MVCC
![image-20210813102852376](/Users/lindinghao/Library/Application Support/typora-user-images/image-20210813102852376.png)
可重复读级别下
Q1为3,Q2为1
k这一行数据会有多个版本号,这个版本号就是每个事务的transactionId(简称t_id),t_id是每个事务的唯一主键,是每个事务开始的时候向INNODB的事务系统申请的,是按申请顺序严格递增的,每次事务更新数据的时候,都会生成一个新的数据版本,并且把t_id赋值给这个数据版本的事务ID ,记为row t_id ,同时,这行数据旧的数据版本也要保留。
也就是说,数据表中的一行记录,其实可能有多个版本,每个版本有自己的row t_id
![image-20210813103537224](/Users/lindinghao/Library/Application Support/typora-user-images/image-20210813103537224.png)
按照可重复读的定义,一个事务启动的时候,能够看到所有已经提交的事务结果,但是之后,这个事务执行期间,其它事务的更新对它不可见。
因此,INNODB代码实现上,一个事务只需要在启动的时候,找到所有已经提交的事务ID的最大值,记为Up_limit_id,如果一个数据版本的row t_id大于up_limit_id,就不承认这个版本,必须要找到它的上一个版本。当然,如果一个事务自己更新的数据,自己还是要认的。
后面新的事务产生的row t_id肯定是大于up_limit_id的,对当前事务肯定是不可见的,但如果说,有一个事务早于当前事务启动,并且在后面更新了数据,由于这个事务早于当前事务开启,t_id也就小于当前事务的t_id,那么,当前事务会认可这个数据版本吗? 答案是不会的,因为除了上面说的那个规则,还有一个规则就是:事务启动的时候还要保存“现在正在执行的所有事务ID列表” ,如果一个row t_id在这列表中,也要不可见。
接下来,看一下为什么Q2的结果是1;
假设: 事务A开始前,系统里已经提交的事务最大ID,即up_limit_id是99;
事务A、B、C的版本号分别是100、101、102,且当前系统里没有别的事务;
三个事务开始前,(1,1)这一行数据的row t_id是90。
这样,事务A、B、C的up_limit_id都是99
![image-20210813105845254](/Users/lindinghao/Library/Application Support/typora-user-images/image-20210813105845254.png)
流程:第一个有效更新是事务C,把数据从(1,1)改成了(1,2)。这个时候,这条数据的最新版本的rowt t_id是102,而90成了历史版本。
第二个有效更新是事务B,把数据从(1,2)改成了(1,3),这个数据的最新版本row t_id是101,102成了历史版本。
现在事务A来读数据了,它的up_limit_id是99。
找到(1,3)的时候,判 断row t_id = 101 > 99,要不起;
找上一个历史版本,一看row t_id = 102;还是要不起;
往前找,(1,1),row t_id = 90 ,是可见数据了。
(1,1)这个历史版本在事务A提交后,就会被删掉。
假设事务A在读取K之前先执行一次update语句,会是什么结果?
事务A会拿到最新的数据,(1,3)进行update变成(1,4),再次执行select,拿出来就是(1,4)了。
因为更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读” current read;
所以更新的时候,就把row t_id更新为100了,自己的up_limit_id也变成了100,查询的时候,row t_id = up_limit_id ,是可见的数据了。
如何解决幻读
快照读的幻读是用MVCC解决的,当前的读的幻读是用间隙锁解决的。
innodb的默认事务隔离级别是rr(可重复读)。它的实现技术是mvcc。该技术不仅可以保证innodb的可重复读,而且可以防止幻读。(这也就是是此前以rr隔离级别实践时,不仅可以防止可重复读,也防止了幻读)但是它防止的是快照读,也就是读取的数据虽然是一致的,但是数据是历史数据。
这个帖子里面就有一个实例:MySQL的InnoDB的幻读问题
一些文章写到InnoDB的可重复读避免了“幻读”(phantom read),这个说法并不准确。
那InnoDB指出的可以避免幻读是怎么回事呢?
以下翻译自MySQL官网文档(http://download.nust.na/pub6/mysql/doc/refman/5.5/en/innodb-next-key-locking.html),翻译水平一般,请见谅。
当隔离级别是可重复读,且禁用innodb_locks_unsafe_for_binlog的情况下,在搜索和扫描index的时候使用的next-key locks可以避免幻读。也就是说的间隙锁。
InnoDB提供了next-key locks,但需要应用程加锁
举个例子:
1 |
|