在数据库事务并发的情况下,会遇到不可重复读的问题
不可重复读就是说事务A需要执行两次查询,这时候一个事务B对事务A两次查询的间隙中把某些行给update了,然后事务A两次读出来的数据就不一样
要解决这样的问题,一般能想到两种办法:将事务要查询的某些行给上锁和给数据表添加两个版本字段,创建版本和删除版本
第一种方法效率较低,第二种方法就是MVCC
MVCC只在REPEATABLE READ和READ COMMITED两个隔离级别下工作,其它两个隔离级别下不存在MVCC
实现的原理
数据表的每一行引擎innodb都会添加上创建版本号和删除版本号,版本号是递增的,有点类似与git上面每次commit都会有一个版本号,版本号保证是唯一的。
具体操作
select:满足以下两个条件要同时成立innodb才会返回该行数据:(1)该行的创建版本号小于等于当前版本号,用于保证在select操作之前所有的操作已经执行落地。(2)该行的删除版本号大于当前版本或者为空。删除版本号大于当前版本意味着有一个并发事务将该行删除了。
insert:将新插入的行的创建版本号设置为当前系统的版本号。
delete:将要删除的行的删除版本号设置为当前系统的版本号。
update:不执行原地update,而是转换成 delete+insert。将旧行的删除版本号设置为当前版本号,并将新行insert同时设置创建版本号为当前版本号。-->这是构思非常巧妙地
insert,delete,update执行的时候,系统版本号会递增
怎么解决不可重复读的呢?
比如,事务A要执行两次查询,事务B在事务A查询的两次间隙中对某些数据进行update,假设查询的那几行数据之前都只做过insert,insert会给该行的创建版本给填上初始版本号,然后系统版本号也设置为这个0.0.1,假如是0.0.1,此时我们进行select的话则用系统当前版本号0.0.1与那几行数据的创建版本号也是0.0.1进行比较,很显然,该行的创建版本号要<=系统当前版本号,这样才能保证select之前的操作已经执行完毕,因为我要读的要是最新的数据,如果创建版本号>当前版本号,不就是说select之前的(insert,update)操作将创建版本号给改小了?但是第二个条件无法满足,所以无法读出来
依旧无法解决幻读问题
select才会去判断当前版本号和数据的创建版本号,删除版本号的关系
update只是给旧行打上了标记,但是并没有物理上的删除,所以需要有后台线程将这些删除版本号小于系统当前版本号的行删除
如果要防止幻读的话,MVCC的基础上还需要上锁