MySQL:MVCC


风晓
风晓 2023-12-28 13:53:24 52346 赞同 0 反对 0
分类: 资源
主要讲MySQL:MVCC

提到事务都会想起的四个特性ACID以及底层原理

原子性(atomicity):一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。要不全部成功,要不全部失败 底层是:undo log(MVCC)

一致性(consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。通过原子性,隔离性,持久性来保证实现 ,最核心最本质的要求

隔离性(isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。通过锁,MVCC来保证实现,在这里就有隔离级别

持久性(durability):指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。在某个事务的执行过程中,对数据所做的所有改动都必须在事务成功结束前保存在某种存储设备中。这样可以保证所做的修改在任何系统瘫痪时不至于丢失通过redo log来保证实现。

数据库的事务的隔离级别有4种:按照优先级从低到高依次是:

read uncommitted; 	--读未提交   
read commited;		--读已提交
repeatable read;	--可重复读
seariable	        --序列化执行,串行执行

不同的隔离级别下会产生脏读,幻读,不可重复读等问题。因此选择隔离级别的时候要根据场景来决定。

read uncommitted(读未提交):会产生脏读,事务1读到了事务2还没有提交的数据,这就是脏读。

 

read commited(读已提交)会产生不可重复读,事务1读取某一数据,事务2读取并修改了该数据,事务1为了对读取值进行检验而再次读取该数据,便得到了不同的结果。因为两次读取的数据不一样,即原始的数据不可以重复读取。就叫做不可重复读。

虽然read commited避免了脏读,但是带来了不可重复读。如何解决不可重复读呢?

Repeatable read(可重复读):

当隔离级别设置为Repeatable read时,可以避免不可重复读。但是带来了幻读。
幻读:
前提条件:InnoDB引擎,可重复读隔离级别,使用当前读时。
表现:一个事务(同一个read view)在前后两次查询同一范围的时候,后一次查询看到了前一次查询没有看到的行。两点需要说明
  1、在可重复读隔离级别下,普通查询是快照读,是不会看到别的事务插入的数据的,幻读只在当前读下才会出现。
  2、幻读专指新插入的行,读到原本存在行的更新结果不算。因为当前读的作用就是能读到所有已经提交记录的最新值。

大多数数据库的默认级别就是Read committed,比如Sql Server , Oracle

但是MySQL的默认隔离级别是Repeatable read(可重复读)

 


什么是MVCC? MVCC :多版本并发控制

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

MVCC在mysql innodb中的实现主要是为了提高数据库并发性能,用跟好的方式去处理读写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读。

 

 

1、当前读(读取时要保证其他并发事务不能修改当前记录,会对读取的记录加锁)

当前读一定是读取的是记录的最新版本,select lock in share mode(共享锁),select for update,update,insert,delete这些操作都是一种当前读。

2、快照读(前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读)

不加锁的select操作就是快照读,即不加锁的非阻塞读,快照读的出现是基于提高并发性能的考虑,快照读的实现是基于MVCC,MVCC可以认为是行锁的一个变种,避免了加锁操作,降低开销,既然MVCC是多版本并发控制,字面上意思都知道了,可能读取到的并不一定是数据的最新版本。这里想一个问题,如果进行数据值的插入的时候,除了保留最新结果外,还会有很多历史版本,历史版本都存在undolog里,磁盘上,回滚的时候只需要找到当前行的上一个版本的数据就行了。当前行的历史版本数据存在undolog里,从undolog里取到当前行的结果返回就ok了

3、当前读,快照读,MVCC关系

MVCC(多版本并发控制)指的是维持一个数据的多个版本,使得读写操作没有冲突,快照读是MySQL为实现MVCC的一个非阻塞的读功能,MVCC模块在MySQL中的具体实现是由三个隐式字段、undolog、read view 这三个组件来实现的。

4、MVCC解决的问题

数据库并发场景有三种,分别为:

1、读读:不存在任何问题,也不需要并发控制

2、读写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读、幻读、不可重复读

3、写写:有线程安全问题,可能存在更新丢失问题

MVCC是一种用来解决读写冲突的无锁并发控制,也就是为事务分配单项增长的时间戳,为每一个修改保存一个版本,版本与事务时间错关联,读操作只读该事务开始前的数据库的快照,所以MVCC可以为数据库解决以下问题:

1、并发读写DB时候,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能。

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

5、MVCC实现原理

通过记录中的三个隐藏字段,undolog,read view来实现的。

隐藏字段

每行记录除了自定义的字段外,还有数据库隐式定义的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等字段

DB_TRX_ID

6字节,最近修改事务id,记录创建这条记录或者最后一次修改该记录的事务id

DB_ROLL_PTR

7字节,回滚指针,指向这条记录的上一个版本,用于配合undolog,指向上一个旧版本

DB_ROW_JD

6字节,隐藏的主键,如果数据表没有主键,那么innodb会自动生成一个6字节的row_id

 

undo log (数据结构为链表)

undolog被称之为回滚日志,表示在进行insert,delete,update操作的时候产生的方便回滚的日志

当进行insert操作的时候,产生的undolog只在事务回滚的时候需要,并且在事务提交之后可以被立刻丢弃,注意了,这里一定要想清楚undolog,多个版本的历史数据会保留吗?肯定不会的,如果都保留那么undolog文件得多大呀,持久化到磁盘也占用很大空间,所以仔细想undolog是回滚用的,回滚是事务执行异常的时候才会出现的操作,而当事务执行成功直接提交了undolog就直接删除了。这只是在insert操作才这样,(update和delete操作和快照读是不能删除undolog的)

当进行update和delete操作的时候,产生的undolog不仅仅在事务回滚的时候需要,在快照读的时候也需要,所以不能随便删除,只有在快照读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除

下面看一下undolog生成的记录链

1、假设有一个事务编号为1的事务向表中插入一条记录,那么此时行数据的状态为:

2、假设有第二个事务编号为2对该记录的name做出修改,改为lisi

在事务2修改该行记录数据时,数据库会对该行加排他锁

然后把该行数据拷贝到undolog中,作为 旧记录,即在undolog中有当前行的拷贝副本

拷贝完毕后,修改该行name为lisi,并且修改隐藏字段的事务id为当前事务2的id,回滚指针指向拷贝到undolog的副本记录中,事务提交后,释放锁

3、假设有第三个事务编号为3对该记录的age做了修改,改为32

在事务3修改该行数据的时,数据库会对该行加排他锁

然后把该行数据拷贝到undolog中,作为旧纪录,发现该行记录已经有undolog了,那么最新的旧数据作为链表的表头,插在该行记录的undolog最前面

修改该行age为32岁,并且修改隐藏字段的事务id为当前事务3的id,回滚指针指向刚刚拷贝的undolog的副本记录 事务提交,释放锁

从上述的一系列图中,可以发现,不同事务或者相同事务的对同一记录的修改,会导致该记录的undolog生成一条记录版本线性表,即链表,undolog的链首就是最新的旧记录,链尾就是最早的旧记录。

 

Read View(请记住read view是事务执行快照读的时候生成的。)

上面的流程如果看明白了,再深入理解下read view的概念。

Read View是事务进行快照读操作的时候生产的读视图在该事务执行快照读的那一刻,会生成一个数据系统当前的快照,记录并维护系统当前活跃事务的id,事务的id值是递增的。

其实Read View的最大作用是用来做可见性判断的,也就是说当某个事务在执行快照读的时候,对该记录创建一个Read View的视图,把它当作条件去判断当前事务能够看到哪个版本的数据,有可能读取到的是最新的数据,也有可能读取的是当前行记录的undolog中某个版本的数据

Read View 遵循的可见性算法主要是将要被修改的数据的最新记录中的DB_TRX_ID(当前事务id)取出来,与系统当前其他活跃事务的id去对比,如果DB_TRX_ID跟Read View的属性做了比较,不符合可见性,那么就通过DB_ROLL_PTR回滚指针去取出undolog中的DB_TRX_ID做比较,即遍历链表中的DB_TRX_ID,直到找到满足条件的DB_TRX_ID,这个DB_TRX_ID所在的旧记录就是当前事务能看到的最新老版本数据。这个是重点,就是匹配到相同的DB_TRX_ID,此时的这条记录就是事务能看到的最新的老版本数据。

 

Read View的可见性规则如下所示:

首先要知道Read View中的三个全局属性:

trx_list:一个数值列表,用来维护Read View生成时刻系统正活跃的事务ID(1,2,3)

up_limit_id:记录trx_list列表中事务ID最小的ID(1)

low_limit_id:Read View生成时刻系统尚未分配的下一个事务ID,(4)

具体的比较规则如下:

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

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

3、判断DB_TRX_ID是否在活跃事务中,如果在,则代表在Read View生成时刻,这个事务还是活跃状态,还没有commit,修改的数据,当前事务也是看不到,如果不在,则说明这个事务在Read View生成之前就已经开始commit,那么修改的结果是能够看见的。

 

这个trx_list 在上图的例子中就是 1,2,3      up_limit_id :1         low_limit_id:4

下来用一个图来看一下比较规则中的第三点:

即:判断DB_TRX_ID是否在活跃事务中,如果在,则代表在Read View生成时刻,这个事务还是活跃状态,还没有commit,修改的数据,当前事务也是看不到,如果不在,则说明这个事务在Read View生成之前就已经开始commit,那么修改的结果是能够看见的。其实Read View的最大作用是用来做可见性判断的

 

从上述表格中,可以看到,当事务2对某行数据执行了快照读,数据库为该行数据生成一个Read View视图,可以看到事务1和事务3还在活跃状态,事务4在事务2快照读的前一刻提交了更新,所以,在Read View中记录了系统当前活跃事务1,3,维护在一个列表中。同时可以看到up_limit_id的值为1,而low_limit_id为5,如下图所示

在上述例子中,只有事务4修改过该行记录,并在事务2进行快照读之前提交了事务,所以事务4会产生一个undolog

当事务2在快照读 该记录的时候,会拿着该行记录的DB_TRX_ID去跟up limit id ,lower limit id,以及活跃事务列表进行比较,判断事务2能看到该行记录的哪个版本。注意了,例子是要验证 事务2能不能读到事务4修改后的记录,所以当事务2在快照读 该记录的时候,这个该记录就是事务4修改后的这个记录。。。会拿着该行记录的DB_TRX_ID去跟up limit id ,lower limit id,以及活跃事务列表进行比较,这里的该行记录的DB_TRX_ID就是事务4的DB_TRX_ID=4,所以现在根据规则来比较一下:

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

2、接下来判断DB_TRX_ID >= low_limit_id,如果大于等于则代表DB_TRX_ID所在的记录在Read View生成后才出现的,那么对于当前事务肯定不可见,如果小于,则进入下一步判断(4>=5 不符合)

3、判断DB_TRX_ID是否在活跃事务中,如果在,则代表在Read View生成时刻,这个事务还是活跃状态,还没有commit,修改的数据,当前事务也是看不到,如果不在,则说明这个事务在Read View生成之前就已经开始commit,那么修改的结果是能够看见的。判断一下事务4是否在活跃事务中呢?活跃事务是123,因为事务4已经提交了,不属于活跃事务了,所以4不在活跃事务中,由条件可以知道,如果不在活跃事务中,那就说明事务4在事务2快照读生成Read View生成之前就已经开始commit,那么修改的结果是能够看见的。

所以事务2的快照读是可以读到事务4的update的结果的。

一定要把这块 和 幻读分清楚,幻读是当前读看见了insert的数据

幻读的前提是InnoDB引擎,可重复读隔离级别,使用当前读

表现:一个事务(同一个read view)在前后两次查询同一范围的时候,后一次查询看到了前一次查询没有看到的行。

两点需要说明:   

1、在可重复读隔离级别下,普通查询是快照读,是不会看到别的事务插入的数据的,幻读只在当前读下才会出现。   

2、幻读专指新插入的行,读到原本存在行的更新结果不算。因为当前读的作用就是能读到所有已经提交记录的最新值。



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

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

如果您发现该资源为电子书等存在侵权的资源或对该资源描述不正确等,可点击“私信”按钮向作者进行反馈;如作者无回复可进行平台仲裁,我们会在第一时间进行处理!

评价 0 条
风晓L1
粉丝 1 资源 2038 + 关注 私信
最近热门资源
银河麒麟桌面操作系统备份用户数据  129
统信桌面专业版【全盘安装UOS系统】介绍  126
银河麒麟桌面操作系统安装佳能打印机驱动方法  119
银河麒麟桌面操作系统 V10-SP1用户密码修改  108
麒麟系统连接打印机常见问题及解决方法  20
最近下载排行榜
银河麒麟桌面操作系统备份用户数据 0
统信桌面专业版【全盘安装UOS系统】介绍 0
银河麒麟桌面操作系统安装佳能打印机驱动方法 0
银河麒麟桌面操作系统 V10-SP1用户密码修改 0
麒麟系统连接打印机常见问题及解决方法 0
作者收入月榜
1

prtyaa 收益393.62元

2

zlj141319 收益218元

3

1843880570 收益214.2元

4

IT-feng 收益210.13元

5

风晓 收益208.24元

6

777 收益172.71元

7

Fhawking 收益106.6元

8

信创来了 收益105.84元

9

克里斯蒂亚诺诺 收益91.08元

10

技术-小陈 收益79.5元

请使用微信扫码

加入交流群

请使用微信扫一扫!