MySQL MVCC机制解析

1、MVCC(multi version concurrent control)机制

多版本并发控制机制,专门控制多个事物并发运行的时候,互相之间会如何影响。

MVCC机制 = undo log多本本链条+ReadView机制

1)ReadView:执行一个事务,会生成一个ReadView

关键的四个字段

  • m_ids:有哪些事务在MySQL里执行还没提交
  • min_trx_id:m_ids里最小的值
  • max_trx_id:mysql下一个要生成的事务id,最大的事务id
  • creator_trx_id:现在事务的id

假设原来数据库有一行数据,事务id是32,他的值就是初始值,如下图所示。

 此时有两个事务并发执行,事务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第一次查询这行数据,会判断当前这行数据的trx_id是否小于ReadView中的min_trx_id,此时发现txr_id是32,小于ReadView的min_trx_id,也就是45,说明事务开启之前,修改这行数据的事务早就提交了,此时可以查到这行数据,如下图所示:

事务B开始执行,将这行数据改为值B,txr_id设置为自己的id,59,同时roll_pointer指向修改之前生成的undo log,如下图所示: 

 这个时候事务A再次查询,此时数据行里txr_id=59,那么这个txr_id大于ReadView里的min_txr_id(45),小于ReadView中的max_trx_id(60),说明更新这条数据的事务,是跟自己差不多同时开启的,不可以查这条数据。

顺着这条数据的roll pointer顺着undo log日志链条往下找,就会找到最近的一条undo log , trx id是32,此时发现trx id=32,是小于ReadView里的min trx id (45 )的,说明这个undo log版本必然是在事务A开启之前就扶行且提交的。查询最近的那个undo log里的值好了,这就是undo log多版本链条的作用,他可以保存一个快照链条,让你可以读到之前的快照值。

通过这套ReadView + undo log 日志链条的机制可以保证事务A不会读到并发执行的事务B更新呢的值,只会读到之前的值。

此时事务A自己更新这行数据,改成值A,trx_id修改为45。

事务A 再次来查询这条数据的值,发现trx_id,与自己ReadView中的creator_trx_id是一样的,说明这行数据就是自己修改的,因此可以看到这条数据。如下图:

此时事务A执行的过程中,突然开启了事务C,这个事务的id是78,然后他更新了哪行数据的值为值C ,并且提交,如下图:

此时事务A再去查询,会发现当前数据的trx_id=78,大于自己ReadView中的max_trx_id(60),说明这条数据是事务A开启后,新的事务来更新了数据,因此看不到这条数据。如下图:

 事务A会顺这undo log链条往下找,先找到值A自己修改过的那个版本,因为trx_id=45与自己ReadView中的Creator_trx_id是一样的,所以读取trx这条数据。如下图:

 通过undo log+ReadView机制,保证你只能读到你事务开启之前的数据,别的提交事务更新的值,还有就是你自己事务更新的值。可以实现多个事务并发执行时候的数据隔离。

2、RC(Read Committed)基于ReadVIew的实现

核心:一个事务设置处于RC隔离级别的时候,他每次发起查询,都重新生成一个ReadView。

本质:协调多个事务并发运行的时候,并发的读写同一批数据,协调互相的可见性。

解析:

此时数据库有一条事务id=50的数据,另外活跃的两条事务,事务A(id=60),事务B(id=70)。如下图:

当事务B发起一次update操作,更新了这条数据,把这条数据的值修改为了值B,此时数据的trx_id变为事务B的id=70,同时生成一条undo log,由roll_pointer指向trx_id=50的数据。如下图:

 此刻事务A发起查询,生成一个ReadView,此时ReadView里的min_trx_id=60,max_trx_id=71,creator_trx_id=60,如下图所示:

 事务A发现当前这条数据的trx_id=70,属于ReadView的事务id范围之内,说明是他生成ReadView之前就有这个活跃的事务B,事务B修改了这条数据的值,还没有提交,所以ReadView的m_id活跃事务列表里,有[60,70]两个id,所以此时根据ReadView的机制,此时事务A无法查到事务B修改的值B。

顺着链条下找,发现trx_id=50的数据,小于当前ReadView中的min_trx_id=60,说明这条数据是事务A生成ReadView之前插入的并且提交了,因此可以查到,如下图所示:

此时事务B提交后,事务A再次发起查询,再次生成一个ReadView,现在活跃的事务只有事务A,所以min_trx_id=60,max_trx_id=71,m_ids活跃事务列表中只会有一个60,,如下图:

事务A再次基于ReadView查询,发现这条数据的trx_id =70,虽然在ReadView的min_trx_id和max_trx_id范围之间,但是并不在m_ids列表中,说明事务B在事务A生成ReadView之前就提交了,此时事务A可以查询到事务B。如下图所示:

3、RR(Repeatable Read)基于ReadView的实现

RR级别下,事务读一条数据,无论读多少次,都是一个值,别的事务修改数据之后哪怕提交了,你也看不到人家修改的值,避免了不可重复读的问题;如果别的事务插入一些新数据,你也是读不到的,避免了幻读的问题。

1)、解决不可重复读

数据库有一条事务id=50的数据,此时有两条事务同时运行,事务A(id=60),事务B(id=70)。如下图所示

 事务A发起查询并生成一个ReadView,此时ReadView里的creator_trx_id=60,min_trx_id=60,max_trx_id是71,m_ids是[60,70]。如下图所示:

事务A基于自身的ReadView去查询这条数据,会发现这条数据的trx_id为50,是小于ReadView里的min_trx_id,可以查到这条原始值

事务B此时更新了这条数据的值为值B,此时会修改trx_id为70,同时生成一个undo log,并且提交事务。如下图所示:

ReadView一旦生成就不会改变,虽然事务B结束了,但是事务A的ReadView中m_ids还是会有[60,70]两个事务id。

此时事务A去查询这条数据,会发现此时的数据是trx_id=70,还在m_ids列表中,事务A是不能查询到书屋B更新的这个值,因此会继续顺着指针去链表中找。如下图所示:

事务找到trx_id=50这条数据,小于本身ReadView中的min_trx_id(60),此时事务A可以查询到这条数据。如图示:

 此刻避免了不可重复读

2)、解决幻读

事务A使用“select * from table where id > 10”来查询,查到trx_id=50的数据(查不到trx_id=70数据的原因不再做描述,见上方解析)如下图:

 事务C插入一条数据并且提交

 此时事务A再次查询,发现trx_id=50,trx_id=80都两条数据,但是trx_id=80大于自己ReadView中max_trx_id(71),说明此条数据是事务A执行后才开始执行的,不能查询。如下图所示:

 因此事务A根本不会发生幻读。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值