MVCC如何实现的隔离级别

一:背景

在面试的时候经常会问到关于mysql的一些知识,有些面试官就特别喜欢用些专业术语,装逼开航母嘛,特此总结MVCC。

二:先了解一些专业名词

事务的四大特性ACID

  • 原子性:
    事务的最小单元,不可分割,要么全部成功,要么全部失败。
  • 一致性:
    事务开始和结束后,数据库的完整性不会被破坏。
  • 隔离性:
    不同事务之间互不影响,四种隔离级别RU(读未提交),RC(读已提交)、RR(可重复读)、SERIALIZABLE(串行化)。
  • 持久性:
    事务提交后,对数据的修改是永久性的,即使系统故障也不会丢失。

数据库读现象

ACID属性里面有一个隔离级别,即并发事务之间互相不干扰只是一个终极状态,且需要消耗巨大的性能。在我们实际应用过程中,是存在很大的灰度空间的,隔离级别有程度的区分。所以隔离级别比较弱的话,就会产生脏读,不可重复读,幻读的现象
1,脏读
事务T1读取到了事务T2没有提交的数据
2,不可重复读
在数据访问中,一个事务范围内的两次相同查询却返回了不同的数据。
事务T1读取某一条数据,事务T2读取并修改了该数据,T1为了对读取值进行验证而重新读取,却发现得到了不通的结果。(update)
3,幻读
幻读解决了不可重复读的问题,即在同一个事务范围内,两次相同的查询结果是相同的,但是可以新增表中的数据记录。
幻读是指事务T1对表中的数据进行修改,假设修改涉及了表中的全部的数据行,同时第二个事务也修改了这个表中的数据,这种修改是向表中插入一条新的数据。后面就会出现操作了T1事务的用户发现表中还有没有修改的数据行,仿佛出现了幻觉一样。

事务的隔离级别

  • 读未提交(Read Uncommitted/RU)
    别称脏读,一个事务可以读取到另一个事务未提交的数据。
    这种隔离级别为最不安全的一种,因为未提交的事务是存在回滚的情况。
  • 读已提交(Read Committed/RC)
    别称为不可重复读,一个事务因为读取到另一个事务已提交的修改数据,导致当前事务的不同时间读取同一条数据获取的结果不一致。
    eg:
    下列例子会发现SessionA再同一个事务期间两次查询的数据不一样。原因就是在于当前隔离级别是RC,SessionA的事务可以读取到SessionB提交的最新数据。
    在这里插入图片描述
  • 可重复读(Repeatable Read/RR)

又称为幻读,一个事务可以读取到其他事务提交的数据,但在RR隔离级别下,当前读取此条数据只可读取一次,在当前事务中,不论读取多少次,数据仍然是第一次读取的值,不会因为在第一次读取之后,其他事务再修改提交数据而产生改变。因此也成为幻读,因为读出来的数据并不一定就是最新的数据。
eg:
在SessionA中第一次读取数据时,后续其他事务修改提交数据,不会影响到SessionA读取的数据值。此为可重复读。
在这里插入图片描述

  • 串行化(Serializable)

所有的数据库读或者写操作都为串行执行,当前隔离级别下只支持请求单个请求同时执行,所有的操作都需要队列执行。所以这种隔离级别下所有的数据是最稳定的,但是性能也是最差的。
数据库的锁实现就是这种隔离级别更小粒度的版本。
在这里插入图片描述

事务和MVCC原理

不同事务同时操作同一条数据产生的问题

示例:
在这里插入图片描述在这里插入图片描述
上列的两种情况就是对于一条数据,多个事务同时操作可能会产生的问题,会出现某个事务的操作被覆盖而导致数据丢失。

LBCC解决数据丢失

LBCC,基于锁的并发控制,Lock Based Concurrency Control。
使用锁的机制,在当前事务需要对数据修改时,将当前事务加上锁,同一个时间只允许一条事务修改当前数据,其他事务必须等待锁释放之后才可以操作。

MVCC解决数据丢失

MVCC,多版本的并发控制,Multi-Version Concurrency Control。

使用版本来控制并发情况下的数据问题,在B事务开始修改账号且事务未提交时,当A事务需要读取账户余额时,此时会读到B事务修改操作之前的账户余额的副本数据,但是如果A事务需要修改账户余额数据就必须要等待B事务提交事务。

MVCC使得数据库读不会数据加锁,普通的Select请求不会加锁,提高了数据库的并发处理能力。

借助MVCC,数据库可以实现READ COMMITTED,REPEATABLE READ等隔离级别,用户可以查看当前数据的前一个或者几个历史版本,保证了ACID中的I特性(隔离性)。

InnoDB的MVCC实现逻辑

InnoDB存储引擎保存的MVCC的数据

InnoDB的Mvcc是通过在每行记录后面保存两个隐藏的列来实现的。一个保存了行的事务ID(DB_TRX_ID),一个保存了行的回滚指针(DB_ROLL_PT)。没开始一个新的事务,都会自动递增产生一个新的事务id。事务开始时刻的会把事务id放到当前事务影响的行事务id中,当查询时需要用当前事务id和每行记录的事务id进行比较。
下面看一下在REPEATABLE READ隔离级别下,MVCC具体时如何操作的。

  • SELECT

innodb会根据以下两个条件检查每行记录。
1,InnoDB只查找版本早于当前事务版本的数据行(也就是,行的事务编号),这样可以确保事务读取的行,要么是在事务开始前已经存在,要么是事务自身插入或者修改过的。
2,删除的行要事务id判断,读到事务开始之前状态的版本,只有符合上述两个条件的记录,才能作为查询结果。

  • INSERT
    InnoDB为新插入的每一行保存事务编号作为版本号
  • DELETE
    InnoDB为删除的每一行保存当前事务编号作为删除标识。
  • UPDATE
    InnoDB为插入一行新纪录,保存当前事务编号为版本号,同时保存当前事务编号到原来的行作为行删除标识。
    保存这两个额外事务编号,使大多数读操作都可以不用加锁。这样设计使得毒素据操作很简单,性能很好,并且也能保证只会读取到符合标准的行。不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作,以及一些额外的维护工作。

MVCC在mysql中实现依赖的是undo log与read view

undo log
根据行为的不同,undo log分为两种:insert undo log和update undo log

  • insert undo log
    insert 操作中产生的undo log,因为insert 操作记录只对当前事务本身可见,对于其他事务记录不可见,所以insert undo log可以在事务提交后直接删除不需要进行purge操作。
    purge 的主要任务是将数据库中已经mark del的数据删除,另外也会批量回收undo pages
    在这里插入图片描述

  • update undo log
    update 或 delete 操作产生的undo log,因为会对已经存在的记录产生影响,为了能提供MVCC机制,因此update undo log不能再事务提交时就进行删除,而是将事务提交时放到history list上,等待purge线程进行最后的删除操作。
    在这里插入图片描述
    在这里插入图片描述
    为了保证事务并发操作时,在写各自的undo log时不产生冲突,innoDB采用回滚段的方式来维护undo log的并发写入和持久化。回滚段实际上是一种undo 文件组织方式。

ReadView

对于RU(READ UNCOMMITTED)隔离级别下,所有事务直接读取数据库的最新之即可,和SERIALIZABLE隔离级别,所有请求都会加锁,同步执行。所以这对两种情况下是不需要使用到Read View的版本控制。

对于RC和RR隔离级别的实现就是通过上面的版本控制完成。两种隔离级别下核心处理逻辑就是判断所有版本中哪个版本是当前事务可见的处理。针对这个问题InnoDB在设计上增加了ReadView的设计,ReadView中主要包含当前系统中还有哪些活跃的读写事务,把他们的事务id放到一个列表中,我们把这个列表命名为m_ids.
对于查询时的版本链数据是否看见的判断逻辑:

  • 如果被访问版本的trx_id属性值小于m_ids列表中最小的事务id,表明生成该版本的事务在生成ReadView前已经提交,所以该版本可以被当前事务访问。
  • 如果被访问版本的trx_id属性值大于m_ids列表中最大的事务id,表明生成该版本的事务在生成ReadView后才生成,所以该版本不可以被当前事务访问
  • 如果被访问版本的trx_id属性值在m_ids列表中最大的事务id和最小的事务id之间,那就需要判断一下trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经提交,该版本可以被访问。
    eg:

Read Commited隔离级别下的ReadView

每次读取数据前都生成一个ReadView(m_ids列表)
在这里插入图片描述

  • 时间点T5情况下的select语句:

在这里插入图片描述
此时 SELECT 语句执⾏,当前数据的版本链如上,因为当前的事务777,和事务888 都未提交,所
以此时的活跃事务的ReadView的列表情况 m_ids: [777, 888] ,因此查询语句会根据当前版本链
中⼩于 m_ids 中的最⼤的版本数据,即查询到的是 Mbappe。

  • 时间点 T8 情况下的 SELECT 语句:
    在这里插入图片描述
    此时 SELECT 语句执⾏,当前数据的版本链如上,因为当前的事务777已经提交,和事务888 未提
    交,所以此时的活跃事务的ReadView的列表情况 m_ids: [888] ,因此查询语句会根据当前版本
    链中⼩于 m_ids 中的最⼤的版本数据,即查询到的是 Messi。
  • 时间点 T11 情况下的 SELECT 语句
    在这里插入图片描述
    此时 SELECT 语句执⾏,当前数据的版本链如上,因为当前的事务777和事务888 都已经提交,所
    以此时的活跃事务的ReadView的列表为空 ,因此查询语句会直接查询当前数据库最新数据,即查
    询到的是 Dybala。
  • 总结:

使⽤READ COMMITTED隔离级别的事务在每次查询开始时都会⽣成⼀个独⽴的 ReadView。
REPEATABLE READ 隔离级别下的ReadView

REPEATABLE READ 隔离级别下的ReadView

在这里插入图片描述

  • 时间点 T5 情况下的 SELECT 语句:
    在这里插入图片描述
    再当前执⾏select语句时⽣成⼀个ReadView,此时 m_ids 内容是: [777,888],所以但前根据
    ReadView可⻅版本查询到的数据为 Mbappe。
  • 时间点 T8 情况下的 SELECT 语句:
    在这里插入图片描述
    此时在当前的 Transaction 999 的事务⾥。由于T5的时间点已经⽣成了ReadView,所以再当前的
    事务中只会⽣成⼀次ReadView,所以此时依然沿⽤T5时的m_ids: [777,999],所以此时查询数据
    依然是 Mbappe。
  • 时间点 T11 情况下的 SELECT 语句
    在这里插入图片描述
    此时情况跟T8完全⼀样。由于T5的时间点已经⽣成了ReadView,所以再当前的事务中只会⽣成⼀
    次ReadView,所以此时依然沿⽤T5时的m_ids: [777,999],所以此时查询数据依然是 Mbappe。

MVCC总结

所谓的MVCC(Multi-Version Concurrency Control,多版本并发控制)指的就是在使用READ COMMITTD、REPEATALBE READ这两种隔离级别的事务在执行普通的select操作时访问记录的版本链的过程,这样子可以是不同事务的读–写、写–读操作并发执行,从而提升系统性能。
在Mysql中,Read Committed 和Read Repeatable Read隔离级别的一个非常大的区别就是它们生成ReadView的时机不同。在Read Committed中每次查询都会生成一个实时的ReadView,做到保证每次提交后的数据是处于当前的可见状态。而RepeaTable Read中,在当前事务第一次查询时生成当前的ReadView,并且当前的ReadView会一直沿用到当前事务提交,以此来保证可重复度(RepeaTable Read)。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值