MySQL InnoDB MVCC的理解

参考了以下博文:

https://www.imooc.com/article/17290

https://baijiahao.baidu.com/s?id=1629409989970483292&wfr=spider&for=pc

MVCC(Mutil-Version Concurrency Control),就是多版本并发控制,实现对数据库的并发访问。

单纯加锁:InnoDB是支持行锁的,这也是防止在多线程的情况下造成数据冲突。但是这种方式时低效的,因为当事务A在对某行数据进行修改的时候加的是排他锁,那么事务B要是想读这条数据就会被堵塞,只能等这行数据的锁被释放(就是事务A提交的时候),才能对数据进行访问。但是数据库一般读操作要大于写操作,而有一个写所有的读都会被堵塞,这是很低效的。

解决办法:MVCCMySQL的实现依赖undo log+read view+排他锁(复制到redo log时用到排他锁)

. 两个关键

  • undo log :记录的是数据表中记录行的多个版本,就是事务执行过程中的回滚段
    • trx_id:记录对某条聚簇索引进行修改的时候的事务id
    • roll_pointer(回滚指针):每次对哪条聚簇索引记录有修改的时候,都会把老版本写入undo日志中。这个roll_pointer就是存了一个指针,它指向这条聚簇索引记录的上一个版本的位置,通过它来获得上一个版本的记录信息(插入操作的的undo日志没有这个属性,因为他没有老版本)
    • 当事务1更改某行的值时,先用排他锁锁定改行,记录redo log,把该行修改前的值Copy到undo log修改当前行的值添加事务编号使roll_pointer回滚指针指向undo log中的修改前的行
    • 当事务2也对改行进行修改(这就是一个版本链
  • read view :每个事务开启的时候都会创建一个 read view read view 中记录着当前所有活跃事务(还没提交的事务) < 不包括本事务 > 。其中比较重要的内容:
    • min_id:所有活跃事务中事务号最小的一个
    • max_id:所有活跃事务中事务号最大的一个
    • create_id:创建这个read view的事务号

 

. 如何进行判断

在读某行的时候需要对事务id进行判断,从而决定这行数据:可以返回当结果 / 不可以的话遍历版本链的下一条记录再进行判断

  1. 如果该行记录的事务id小于当前under viewmin_id,就可以直接返回这条结果(可见的)
  2. 如果该行记录的事务id大于当前under viewmax_id,就是不可见的(这种情况对应的都是在不可重复读下,一个事务A创建之后,另一个事务B再创建,并对这行数据进行修改,当事务A再读这行数据的时候,这行数据记录的trx_id就大于事务A read viewmax_id,所以这条数据数据是不可见的,只能遍历这条数据的旧版本找合适的<因为不可重复读要求当一个事务开启后,别的事务不能再对这行数据进行修改,所以事务B对这行数据的修改,对于事务A是不可见的>
  3. 如果该行记录的事务id(命名为trx_id_row)对于当前under viewmin_id<=trx_id_row<=max_id,那么就遍历under view的事务id集合,如果有trx_id_row,说明trx_id_row这个事务还没有提交,这个版本就是不可见的,如果没有说明已经提交了,就可以访问
  4. 如果该行记录的事务id等于创建under view的事务id,说明读取的是自己本事务修改过的记录,是可以访问的

 

. 不同隔离情况下的under review

  1. 已提交读:每个事务创建的时候都会创建一个under review,并且每次sql语句执行之前都会删掉旧的under review创建一个新的under review
  2. 可重复读:在创建事务的时候创建under review,并且这个under review为全局的,里面的值一直不会发生变化

 

. 举个栗子

事务50和60分别对小明这行进行了修改并提交了

1. 此时有一个事务id为100的事务,修改了name,使得的name等于小明2,但是事务还没提交。则此时的版本链是

那此时另一个事务发起了select 语句要查询id为1的记录,那此时生成的ReadView 列表只有[100]。那就去版本链去找了,首先肯定找最近的一条,发现trx_id是100,也就是name为小明2的那条记录,发现在列表内,所以不能访问。

这时候就通过指针继续找下一条,name为小明1的记录,发现trx_id是60,小于列表中的最小id,所以可以访问,直接访问结果为小明1

2. 这时候我们把事务id为100的事务提交了,并且新建了一个事务id为110也修改id为1的记录,并且不提交事务,则此时的版本链是

这时候之前那个select事务又执行了一次查询,要查询id为1的记录。

关键地方来了:

  • 若是已提交读隔离级别,这时候你会重新一个ReadView,那你的活动事务列表中的值就变了,变成了[110]。那么通过版本链最终得到的结果是小明2
  • 若是可重复读隔离级别,这时候你的ReadView还是第一次select时候生成的ReadView,也就是列表的值还是[100]。所以select的结果是小明1。所以第二次select结果和第一次一样,所以叫可重复读

猜你喜欢

转载自www.cnblogs.com/beeenwei/p/12962049.html