MVCC(Mutil-Version Concurrency Control),就是多版本并发控制。MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问。
一. 多个事务并发更新以及查询数据会产生什么问题?
多个事务并发更新这个过程实际上会涉及到脏写、脏读、不可重复读、幻读,四种问题。
脏写
- 脏写
本质是事务B去修改了事务A修改过的值,但是此时事务A还没提交,所以事务A随时会回滚,导致事务B修改的值也没了。
脏读
- 脏读
本质是事务B去查询了事务A修改过的数据,但是此时事务A还没提交,所以事务A随时会回滚导致事务B再次查询就读不到刚才事务A修改的数据了!
无论是脏写还是脏读,都是因为一个事务去更新或者查询了另外一个还没提交的事务更新过的数据。因为另外一个事务还没提交,所以他随时可能会反悔会回滚,那么必然导致你更新的数据就没了,或者你之前查询到的数据就没了,这就是脏写和脏读两种坑爹场景
不可重复读
- 不可重复读
一个事务多次查询一条数据,结果每次读到的值都不一样,这个过程中可能别的事务会修改这条数据的值,而且修改值之后事务都提交了,结果导致人家每次查到的值都不一样,都查到了提交事务修改过的值,这就是所谓的不可重复读。
幻读
- 幻读
幻读指的就是一个事务用一样的SQL多次查询,结果每次查询都会发现查到了一些之前没看到过的数据。
二. 事务的四个隔离级别
read uncommitted(读未提交):可以避免脏写。
read committed(读已提交):可以避免脏写和脏读。
repeatable read(可重复读):可以避免脏读、脏写和不可重复读,不能避免幻读。
serializable(串行化):不允许你多个事务并发执行,四个问题都可以避免。
MySQL默认的事务隔离级别是RR(可重复读),而且MySQL的RR级别是可以避免幻读发生。也就是说,MySQL里执行的事务,默认情况下不会发生脏写、脏读、不可重复读和幻读的问题。
如何修改MySQL隔离级别?
修改MySQL隔离级别Spring中默认隔离级别与MySQL一致,Spring中如何修改?
修改Spring默认隔离级别三. undo log版本链
undo log版本链在Mysql的InnoDB引擎中就是指在已提交读(READ COMMITTD)和可重复读(REPEATABLE READ)这两种隔离级别下的事务对于SELECT操作会访问版本链中的记录的过程。
这就使得别的事务可以修改这条记录,反正每次修改都会在版本链中记录。SELECT可以去版本链中拿记录,这就实现了读-写,写-读的并发执行,提升了系统的性能。
多个事务串行执行的时候,每个人修改了一行数据,都会更新隐藏字段txr_id和roll_pointer,同时之前多个数据快照对应的undo log,会通过roll_pinter指针串联起来,形成一个重要的版本链!
四.基于undo log多版本链条实现的ReadView机制
简单来说,就是执行一个事务的时候,就生成一个ReadView,里面比较关键的东西有4个:
m_ids:这个就是说此时有哪些事务在MySQL里执行还没提交的;
min_trx_id:就是m_ids里最小的值;
max_trx_id:这是说mysql下一个要生成的事务id,就是最大事务id;
creator_trx_id:就是你这个事务的id
示例:
-
两个事务并发过来执行了,一个是事务A(id=45),一个是事务B(id=59),事务B是要去更新这行数 据,事务A是要去读取这行数据的值。
-
现在事务A直接开启一个ReadView,这个ReadView里的m_ids就包含了事务A和事务B的两个id,45和59,然后min_trx_id就是45,max_trx_id就是60,creator_trx_id就是45,是事务A自己。
-
这个时候事务A第一次查询这行数据,会走一个判断,就是判断一下当前这行数据的txr_id是否小于ReadView中的min_trx_id,此时发现txr_id=32,是小于ReadView里的min_trx_id就是45的,说明你事务开启之前,修改这行数据的事务早就提交了,所以此时可以查到这行数据,如下图所示。
步骤3 -
事务B更新数据,事务A再次查询,此时数据行里的txr_id=59,那么这个txr_id是大于ReadView里的min_txr_id(45),同时小于ReadView里的max_trx_id(60)的,说明更新这条数据的事务,很可能就跟自己差不多同时开启的,于是会看一下这个txr_id=59,是否在ReadView的m_ids列表里?果然,在ReadView的m_ids列表里,有45和59两个事务id,直接证实了,这个修改数据的事务是跟自己同一时段并发执行然后提交的,所以对这行数据是不能查询的!
步骤4 -
事务A自己更新了这行数据的值,改成值A,trx_id修改为45,同时保存之前事务B修改的值的快照。此时事务A来查询这条数据的值,会发现这个trx_id=45,居然跟自己的ReadView里的creator_trx_id(45)是一样的,说明这行数据就是自己修改的啊!自己修改的值当然是可以看到的了!
步骤5 -
接着在事务A执行的过程中,突然开启了一个事务C,这个事务的id是78,然后他更新了那行数据的值为值C,还提交了。这个时候事务A再去查询,会发现当前数据的trx_id=78,大于了自己的ReadView中的max_trx_id(60),说明是这个事务A开启之后,然后有一个事务更新了数据,自己当然是不能看到的了!
步骤6
通过undo log多版本链条,加上你开启事务时候生产的一个ReadView,然后再有一个查询的时候,根据ReadView进行判断的机制,你就知道你应该读取哪个版本的数据。
五. RC隔离级别是如何实现的?
关键点在于每次查询都生成新的ReadView,那么如果在你这次查询之前,有事务修改了数据还提交了,你这次查询生成的ReadView里,那个m_ids列表当然不包含这个已经提交的事务了,既然不包含已经提交的事务了,那么当然可以读到人家修改过的值了
六. 总结
首先我们先要明白,多个事务并发运行的时候,同时读写一个数据,可能会出现脏写、脏读、不可重复读、幻读几个问题。
脏写,就是两个事务都更新一个数据,结果有一个人回滚了把另外一个人更新的数据也回滚没了。
脏读,就是一个事务读到了另外一个事务没提交的时候修改的数据,结果另外一个事务回滚了,下次读就读不到了。
不可重复读,就是多次读一条数据,别的事务老是修改数据值还提交了,多次读到的值不同。
幻读,就是范围查询,每次查到的数据不同,有时候别的事务插入了新的值,就会读到更多的数据。
针对这些问题,所以才有RU、RC、RR和串行四个隔离级别。
RU隔离级别,就是可以读到人家没提交的事务修改的数据,只能避免脏写问题;
RC隔离级别,可以读到人家提交的事务修改过的数据,可以避免脏写和脏读问题;
RR是不会读到别的已经提交事务修改的数据,可以避免脏读、脏写和不可重复读的问题;
串行是让事务都串行执行,可以避免所有问题。
然后MySQL实现MVCC机制的时候,是基于undo log多版本链条+ReadView机制来做的,默认的RR隔离级别,就是基于这套机制来实现的,依托这套机制实现了RR级别,除了避免脏写、脏读、不可重复读,还能避免幻读问题。因此一般来说我们都用默认的RR隔离级别就好了。
网友评论