关于MYSQL InnoDB中的MVCC

参考文章:

mysql 关于innodb中MVCC的一些理解

https://www.cnblogs.com/chenpingzhao/p/5065316.html

MySQL 技术内幕:事务隔离级别和MVCC

http://ningg.top/inside-mysql-transaction-and-mvcc/

InnoDB对MVCC的实现

https://blog.csdn.net/fuzhongmin05/article/details/91351933

Mysql MVCC原理

http://www.woowen.com/mysql/2017/12/23/Mysql%20MVCC%E5%8E%9F%E7%90%86/

MySQL数据库事务各隔离级别加锁情况--read committed && MVCC

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

补充1:

函数:read_view_sees_trx_id。
read_view中保存了当前全局的事务的范围:
【low_limit_id, up_limit_id】
1. 当行记录的事务ID小于当前系统的最小活动id,就是可见的。
  if (trx_id < view->up_limit_id) {
    return(TRUE);
  }
2. 当行记录的事务ID大于当前系统的最大活动id,就是不可见的。
  if (trx_id >= view->low_limit_id) {
    return(FALSE);
  }
3. 当行记录的事务ID在活动范围之中时,判断是否在活动链表中,如果在就不可见,如果不在就是可见的。

(当行记录的事务ID在活动范围之中时,判断是否在活动链表中,如果在就不可见(事务还在执行,没有提交,当然不可见),如果不在就是可见的(曾经在,但是已经提交,当然可见  注意:MVCC是基于RC、RR隔离级别下实现的,只有提交commit后才可见)。)

  for (i = 0; i < n_ids; i++) {
    trx_id_t view_trx_id
      = read_view_get_nth_trx_id(view, n_ids - i - 1);
    if (trx_id <= view_trx_id) {
    return(trx_id != view_trx_id);
    }
  }

补充2:

MVCC所描述的理想的MVCC是:

A.每行数据都存在一个版本,每次数据更新时都更新该版本。
B.修改时Copy出当前版本随意修改,各个事务之间无干扰。
C.保存时比较版本号,如果成功(commit),则覆盖原记录;失败则放弃copy(rollback)。
就是每行都有版本号,保存时根据版本号决定是否成功,版本号比较规则应该是:假设每次修改数据后版本号+1。
A.保存时新数据的版本号new_version>原数据的版本号old_version(证明该事务在修改数据时没有其他事务修改了原数据),则保存成功。

B.保存时新数据的版本号new_version<=原数据的版本号old_version(证明该事务在修改数据时有其他事务修改了原数据),则保存失败,放弃修改。

MYSQL  InnoDB实现的MVCC是:

A.事务以排他锁的形式修改原始数据(InnoDB的默认隔离级别Repeatable Read(可重复读),实现可重复读的关键在于加写锁即排他锁,避免在两次读取数据中间其他事务对该数据进行更改,导致不可重复读)。
B.把修改前的数据存放于undo log,通过回滚指针与主数据关联。
C.修改成功(commit)啥都不做,失败则恢复undo log中的数据(rollback)。

补充3:

A.InnoDB在REPEATABLE_READ隔离级别下,每次都读取指定的版本,这样保证不会产生幻读,但可能读不到最新的数据。

B.既然InnoDB在执行事务时以排他锁的形式修改原始数据,那么InnoDB实现的MVCC如何实现非阻塞读?(对原数据加上排他锁即写锁后,是不能读原数据的)实现方法是在undo log备份日志中去读!

补充4:InnoDB实现的MVCC有何特殊性

上述更新前建立undo log,根据各种策略读取时非阻塞就是MVCC,undo log中的行就是MVCC中的多版本,这个可能与我们所理解的MVCC有较大的出入,一般我们认为MVCC有下面几个特点:

A.每行数据都存在一个版本,每次数据更新时都更新该版本。
B.修改时Copy出当前版本随意修改,各个事务之间无干扰。
C.保存时比较版本号,如果成功则commit并覆盖原记录;失败则放弃copy(rollback)。
就是每行都有版本号,保存时根据版本号决定是否成功,听起来含有乐观锁的味道,而Innodb的实现方式是:

A.事务以排他锁的形式修改原始数据。
B.把修改前的数据存放于undo log,通过回滚指针与主数据关联。
C.修改成功(commit)啥都不做,失败则恢复undo log中的数据(rollback)。


二者最本质的区别是,当修改数据时是否要排他锁定,如果锁定了还算不算是MVCC。

MVCC可以保证不阻塞地读到一致的数据。但是MVCC理论并没有对实现细节做约束,为此不同的数据库的语义有所不同,比如:

A.postgres 对写操作也是乐观并发控制;在表中保存同一行数据记录的多个不同版本,每次写操作都是创建,而回避更新;在事务提交时,按版本号检查当前事务提交的数据是否存在写冲突,则抛异常告知用户,回滚事务。
B.innodb 则只对读无锁,写操作仍是上锁的悲观并发控制,这也意味着,innodb中只能见到因死锁和不变性约束而回滚,而见不到因为写冲突而回滚;不像 postgres 那样对数据修改在表中创建新纪录,而是每行数据只在表中保留一份,在更新数据时上行锁,同时将旧版数据写入 undo log;表和 undo log 中行数据都记录着事务ID,在检索时根据事务隔离级别去读取行数据。可见 MVCC中的写操作仍可以按悲观并发控制实现。
C.MVCC解决的问题是读写互相不阻塞的问题,每次更新都产生一个新的版本,读的话可以读历史版本。试想,如果一个数据只有一个版本,那么多个事务对这个数据进行读写是不是需要读写锁来保护? 一个读写事务在运行的过程中在访问数据之前先加读/写锁这种实现叫做悲观锁,悲观体现在,先加锁,独占数据,防止别人加锁乐观锁呢,读写事务,在真正的提交之前,不加读/写锁,而是先看一下数据的版本/时间戳,等到真正提交的时候再看一下版本/时间戳,如果两次相同,说明别人期间没有对数据进行过修改,那么就可以放心提交。乐观体现在,访问数据时不提前加锁。在资源冲突不激烈的场合,用乐观锁性能较好。如果资源冲突严重,乐观锁的实现会导致事务提交的时候经常看到别人在他之前已经修改了数据,然后要进行回滚或者重试,还不如一上来就加锁。
以上补充内容均来自参考文章!

总结:

1、MVCC可以理解为行级锁的升级版,针对对数据行加行级锁写数据时,读数据行阻塞导致性能下降的问题进行优化。

2、InnoDB中MVCC的实现基于数据行的DATA_TRX_ID和DATA_ROLL_PTR属性,DATA_TRX_ID表示最近更改该行数据的事务ID,DATA_ROLL_PTR是undo log的指针,用于记录之前历史数据在undolog文件中的位置。

3、MVCC解决的问题是读写互相不阻塞的问题,每次更新都产生一个新的版本,读的话可以读历史版本。InnoDB在REPEATABLE_READ隔离级别下,每次都读取指定的版本,这样保证不会产生幻读,但可能读不到最新的数据。

 

欢迎关注我的技术公众号,一起成长!

 

 

posted @ 2019-09-21 21:19  春风十里骚的起  阅读(356)  评论(0编辑  收藏  举报