首页 > 编程知识 正文

mvcc多版本并发控制,elasticsearch原理图

时间:2023-05-05 09:18:51 阅读:166623 作者:3221

什么是MVCC? MVCC

MVCC,全称多版本控制,即多版本并发控制。 MVCC是一种并发控制的方法,在数据库管理系统中一般实现对数据库的并发访问,在编程语言中实现事务内存。

mvcc - @百度百科

什么是当前读和快照读?

在学习MVCC多版本并发控制之前,必须了解什么是MySQL InnoDB中的当前读取和快照读取。

33558 www.Sina.com/selectlockinsharemode,select for update; update、insert和delete (独占锁定)这些操作都是当前读取操作。 为什么称为当前读取? 这是读取记录的最新版本,确保其他并发事务无法修改当前记录,然后锁定已读取的记录

当前读操作,如未锁定的select,是快照读取,即未锁定的无阻塞读取; 快照读操作假定隔离级别不是串行级别,而串行级别的快照读操作将退化为当前读操作。 出现快照读是为了提高并发性,快照读的实现基于对多个版本的并发控制。 这意味着,MVCC认为MVCC是一种行锁定变种,但往往会避免锁定操作并减少开销。 因为基于多个版本,所以读取快照不一定是数据的最新版本,而可能是以前的历史版本

快照读

说白了MVCC就是为了实现读-写冲突不加锁,而这个读指的就是快照读, 而非当前读,当前读实际上是一种加锁的操作,是悲观锁的实现

准确地说,MVCC多版本并发控制是“维护一个数据的多个版本,使读写操作没有冲突”的概念。 只是理想的概念

在MySQL中,为了实现这种MVCC的理想概念,MySQL需要提供实现它的具体功能。 快照读是MySQL实现MVCC理想模式的具体非阻塞读功能之一。 与此相对,现在的读取是悲观锁定的具体功能实现

说得再细一点,读快照本身也是一个抽象的概念,还要进一步深入研究。 MySQL中MVCC模型的具体实现通过3个隐含字段、还原日志、Read View等进行,具体可以看到以下MVCC实现原理

MVCC能解决什么问题? 有什么好处?当前读,快照读和MVCC的关系

读取-读取:没有任何问题,也不需要同时控制读取-写入:有线过程的安全问题可能会导致事务隔离性问题,污读、幻读、不可重复的读取-写入

多版本并发控制(MVCC )是一种未锁定的并发控制,可解决读取和写入冲突。 这意味着为事务分配单方面增长的时间戳,并在每次更改时保存版本。 版本与事务的时间戳相关联,读取操作仅读取事务开始前的数据库快照。 因此,MVCC可以解决数据库的以下问题

同时读写数据库时,在读取操作时不需要阻止写入操作,也不需要阻止写入操作,从而提高数据库的同时读取和写入性能

也可以解决脏读、幻读、不可重复读等事务隔离问题,但不能解决更新丢失问题

让我们来总结一下

总之,MVCC是由于细心的发卡商们不满意只让数据库采用悲观的锁这种性能差的形式来解决读写冲突,所以提出的解决方案,数据库中有MVCC

MVCC悲观摇滚

MVCC解决读写冲突,悲观锁定解决写入冲突

MVCC乐观摇滚

MVCC解决读写冲突,乐观锁解决写入冲突

这种组合可以最大化数据库的并发性能,并解决读写冲突和写入冲突带来的问题

MVCC的实现原理MVCC的目的是多版本并发控制,在数据库中的实现是为了解决读写的冲突,其实现原理主要依靠记录中的3个隐含字段,undo日志,Read View来实现。 我们先来看看这三个点的概念

隐式字段除了我们自定义的字段外,每行记录还包含数据库隐式定义的字段,如DB_TRX_ID、DB_ROLL_PTR和DB_ROW_ID

DB_TRX_ID

6byte,最近更改事务ID (更改/插入) :创建记录/上次修改记录的事务ID

DB_ROLL_PTR

7byte,回滚指向此记录的上一个版本的指针(存储在rollback segment中) )。

DB_ROW_ID

6如果6byte、隐式自增量ID (隐藏主键)或数据表中没有主键,InnoDB会自动使用DB_ROW_ID生成聚簇索引

实际上,还有删除标志隐藏字段。 两个记录都被更新或删除了,这意味着并不是真的被删除,删除标志已经改变

如上图所示,DB_ROW_ID是数据库缺省为行记录生成的唯一隐式主键,DB_TRX_ID是当前正在操作记录的事务ID,db _ roll

还原日志还原日志主要分为两种。

插入还原日志


代表事务在insert新记录时产生的undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃update undo log
事务在进行update或delete时产生的undo log; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除

对MVCC有帮助的实质是update undo log ,undo log实际上就是存在rollback segment中旧记录链,它的执行流程如下:

一、 比如一个有个事务插入persion表插入了一条新记录,记录如下,name为英勇的硬币, age为24岁,隐式主键是1,事务ID和回滚指针,我们假设为NULL


二、 现在来了一个事务1对该记录的name做出了修改,改为Tom

在事务1修改该行(记录)数据时,数据库会先对该行加排他锁
然后把该行数据拷贝到undo log中,作为旧记录,既在undo log中有当前行的拷贝副本
拷贝完毕后,修改该行name为Tom,并且修改隐藏字段的事务ID为当前事务1的ID, 我们默认从1开始,之后递增,回滚指针指向拷贝到undo log的副本记录,既表示我的上一个版本就是它
事务提交后,释放锁

三、 又来了个事务2修改person表的同一个记录,将age修改为30岁

在事务2修改该行数据时,数据库也先为该行加锁
然后把该行数据拷贝到undo log中,作为旧记录,发现该行记录已经有undo log了,那么最新的旧数据作为链表的表头,插在该行记录的undo log最前面
修改该行age为30岁,并且修改隐藏字段的事务ID为当前事务2的ID, 那就是2,回滚指针指向刚刚拷贝到undo log的副本记录
事务提交,释放锁


从上面,我们就可以看出,不同事务或者相同事务的对同一记录的修改,会导致该记录的undo log成为一条记录版本线性表,既链表,undo log的链首就是最新的旧记录,链尾就是最早的旧记录(当然就像之前说的该undo log的节点可能是会purge线程清除掉,向图中的第一条insert undo log,其实在事务提交之后可能就被删除丢失了,不过这里为了演示,所以还放在这里)

Read View(读视图)

什么是Read View?

什么是Read View,说白了Read View就是事务进行快照读操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)

所以我们知道 Read View主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。

Read View遵循一个可见性算法,主要是将要被修改的数据的最新记录中的DB_TRX_ID(即当前事务ID)取出来,与系统当前其他活跃事务的ID去对比(由Read View维护),如果DB_TRX_ID跟Read View的属性做了某些比较,不符合可见性,那就通过DB_ROLL_PTR回滚指针去取出Undo Log中的DB_TRX_ID再比较,即遍历链表的DB_TRX_ID(从链首到链尾,即从最近的一次修改查起),直到找到满足特定条件的DB_TRX_ID, 那么这个DB_TRX_ID所在的旧记录就是当前事务能看见的最新老版本

那么这个判断条件是什么呢?


在展示之前,我先简化一下Read View,我们可以把Read View简单的理解成有三个全局属性

trx_list(名字我随便取的)
一个数值列表,用来维护Read View生成时刻系统正活跃的事务ID

up_limit_id
记录trx_list列表中事务ID最小的ID

low_limit_id
ReadView生成时刻系统尚未分配的下一个事务ID,也就是目前已出现过的事务ID的最大值+1

首先比较DB_TRX_ID < up_limit_id, 如果小于,则当前事务能看到DB_TRX_ID
所在的记录,如果大于等于进入下一个判断

接下来判断 DB_TRX_ID 大于等于 low_limit_id , 如果大于等于则代表DB_TRX_ID 所在的记录在Read
View生成后才出现的,那对当前事务肯定不可见,如果小于则进入下一个判断

判断DB_TRX_ID 是否在活跃事务之中,trx_list.contains(DB_TRX_ID),如果在,则代表我Read
View生成时刻,你这个事务还在活跃,还没有Commit,你修改的数据,我当前事务也是看不见的;如果不在,则说明,你这个事务在Read
View生成之前就已经Commit了,你修改的结果,我当前事务是能看见的

整体流程

我们在了解了隐式字段,undo log, 以及Read View的概念之后,就可以来看看MVCC实现的整体流程是怎么样了

整体的流程是怎么样的呢?我们可以模拟一下

当事务2对某行数据执行了快照读,数据库为该行数据生成一个Read View读视图,假设当前事务ID为2,此时还有事务1和事务3在活跃中,事务4在事务2快照读前一刻提交更新了,所以Read View记录了系统当前活跃事务1,3的ID,维护在一个列表上,假设我们称为trx_list

Read
View不仅仅会通过一个列表trx_list来维护事务2执行快照读那刻系统正活跃的事务ID,还会有两个属性up_limit_id(记录trx_list列表中事务ID最小的ID),low_limit_id(记录快照读那刻系统尚未分配的下一个事务ID(目前已出现过的事务ID的最大值+1))
;所以在这里例子中up_limit_id就是1,low_limit_id就是4 + 1 = 5,trx_list集合的值是1,3,Read
View如下图
我们的例子中,只有事务4修改过该行记录,并在事务2执行快照读前,就提交了事务,所以当前该行当前数据的undo
log如下图所示;我们的事务2在快照读该行记录的时候,就会拿该行记录的DB_TRX_ID去跟up_limit_id,low_limit_id和活跃事务ID列表(trx_list)进行比较,判断当前事务2能看到该记录的版本是哪个。

所以先拿该记录DB_TRX_ID字段记录的事务ID 4去跟Read
View的的up_limit_id比较,看4是否小于up_limit_id(1),所以不符合条件,继续判断 4 是否大于等于
low_limit_id(5),也不符合条件,最后判断4是否处于trx_list中的活跃事务,
最后发现事务ID为4的事务不在当前活跃事务列表中,
符合可见性条件,所以事务4修改后提交的最新结果对事务2快照读时是可见的,所以事务2能读到的最新数据记录是事务4所提交的版本,而事务4提交的版本也是全局角度上最新的版本

也正是Read View生成时机的不同,从而造成RC,RR级别下快照读的结果的不同 MVCC相关问题

RR是如何在RC级的基础上解决不可重复读的?
当前读和快照读在RR级别下的区别:

在上表的顺序下,事务B的在事务A提交修改后的快照读是旧版本数据,而当前读是实时新数据400


而在表2这里的顺序中,事务B在事务A提交后的快照读和当前读都是实时的新数据400,这是为什么呢?

这里与上表的唯一区别仅仅是表1的事务B在事务A修改金额前快照读过一次金额数据,而表2的事务B在事务A修改金额前没有进行过快照读。
所以我们知道事务中快照读的结果是非常依赖该事务首次出现快照读的地方,即某个事务中首次出现快照读的地方非常关键,它有决定该事务后续快照读结果的能力

我们这里测试的是更新,同时删除和更新也是一样的,如果事务B的快照读是在事务A操作之后进行的,事务B的快照读也是能读取到最新的数据的

RC,RR级别下的InnoDB快照读有什么不同?

正是Read View生成时机的不同,从而造成RC,RR级别下快照读的结果的不同

在RR级别下的某个事务的对某条记录的第一次快照读会创建一个快照及Read View,
将当前系统活跃的其他事务记录起来,此后在调用快照读的时候,还是使用的是同一个Read
View,所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个Read View,所以对之后的修改不可见;即RR级别下,快照读生成Read View时,Read
View会记录此时所有其他活动事务的快照,这些事务的修改对于当前事务都是不可见的。而早于Read View创建的事务所做的修改均是可见而在RC级别下的,事务中,每次快照读都会新生成一个快照和Read View,
这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因

当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个Read View,所以对之后的修改不可见;

即RR级别下,快照读生成Read View时,Read
View会记录此时所有其他活动事务的快照,这些事务的修改对于当前事务都是不可见的。而早于Read View创建的事务所做的修改均是可见而在RC级别下的,事务中,每次快照读都会新生成一个快照和Read View,
这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因

总之在RC隔离级别下,是每个快照读都会生成并获取最新的Read View;而在RR隔离级别下,则是同一个事务中的第一个快照读才会创建Read View, 之后的快照读获取的都是同一个Read View。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。