MySQL技术专题探究分析MySQL的MVCC原理介绍
Posted 浩宇の天尚
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL技术专题探究分析MySQL的MVCC原理介绍相关的知识,希望对你有一定的参考价值。
MVCC的基本概念
前提概述
MVCC即多版本并发 控制主要是为了解决数据库中并发事务读写的一致性问题,那么多个事务并发执行的时候事务的隔离到底是怎么实现的呢? mysql默认的RR隔离级别是怎样避免不可重复读的问题呢?我们好好来分析一下。
三种并发场景
- 读-读:不存在任何问题,也不需要并发控制。
- 读-写:线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读。
- 写-写:线程安全问题,可能会存在更新丢失问题。
什么是MVCC
MVCC,全称 Multi-Version Concurrency Control,即多版本并发控制,是乐观锁的一种实现方式,可以做到读不加锁,读写不冲突,解决读-写冲突的无锁并发控制。
注意: MVCC只在Read Commited和Repeatable Read两种隔离级别下工作。
快照读与当前读
快照读:
select操作,不需要加锁,基于MVCC和undo log来实现的,读取的是记录的可见版本(有可能是历史版本)。
当前读:
特殊的读操作,需要加锁,是悲观锁的实现,读取的是记录的最新版本,并且当前读返回的记录,都会加锁,保证其他事务不会再并发修改这条记录。
insert/update/delete
select ...for update
select ... lock in share mode
MVCC的底层实现
- MVCC的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突,它的实现原理主要是依赖记录中的 3个隐式字段,undo log pointer ,Read View,“delete_bit” 来实现的。
- InnoDB MVCC的实现基于undo log,通过回滚指针来构建需要的版本记录。通过ReadView来判断哪些版本的数据可见。同时Purge线程是通过ReadView来清理旧版本数据。
undo log多版本链
MySQL的MVCC机制,以undo log版本链为实现基础。因此要理解MVCC机制,我们得先分析下undo log版本链是个什么东东。Innodb存储引擎给每个数据表都添加了三个隐藏字段:DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID。
- 假设此时如果打南边来了个事务A,它的事务id为12,事务A对表中的数据字段count进行修改,修改后该条数据对应的事务id为12,同时回滚指针指向实际的undo log回滚日志的地址。
如下图所示:
- 此时打北边又来了个事务B,它的事务id为20,事务B将表中的数据字段count修改为21,对应数据的事务id变为20,回滚指针指向上一条undo log信息。
如果一直有事务进行数据的修改,那么就会形成一条由回滚指针串联的undo log多版本链条。
三个隐式字段
DB_TRX_ID
6byte,最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID, 标记更新当前数据记录的transaction id,每处理一个事务,其值自动+1。
DB_ROLL_PTR
7byte,回滚指针,用于配合undo log,指向这条记录的上一个版本,记录了最新一次修改该条记录的undo log地址,回滚的时候就通过这个指针找到undo log回滚。
DB_ROW_ID
6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引(当数据表没有指定主键时,数据库会自动以这个列来作为主键,生成聚集索引。)
- 假设有如下的一条数据,此时这条数据对应的DB_TRX_ID为10,由于之前没有数据,因此回滚指针对应指向空地址。
每个事务都有自己的一个id,就像其身份证一样唯一标记该事务。当事务启动的时候,向Innodb存储引擎进行申请。
DELETE_BIT
删除标志,标志数据是否被删除。实际上每条记录的头信息(record header)里都有一个专门的bit(deleted flag)来表示当前记录是否已经被删除。通过清除purge线程会进行异步解耦方式删除数据信息。
undo log
基本概念
undo log主要记录的是数据的逻辑变化,为了在发生错误时回滚之前的操作,需要将之前的操作都记录下来,然后在发生错误时才可以回滚。
作用
用于事务的回滚
undo日志,用于存放数据修改被修改前的值,如果这个修改出现异常,可以使用undo日志来实现回滚操作,保证事务的一致性。
undo日志,只将数据库逻辑地恢复到原来的样子,回滚的时候,它实际上是做的相反的工作。
用于MVCC
undo log的类型主要分为:
- insert undo log
- update undo log
insert undo log
insert undo log是指在insert 操作中产生的undo log,因为insert操作的记录,只对事务本身可见,对其他事务不可见。故该undo log可以在事务提交后直接删除,不需要进行purge操作。
update undo log
update undo log****记录的是对delete和update操作产生的undo log,该undo log可能需要提供MVCC机制,因此不能再事务提交时就进行删除。提交时放入undo log链表,等待purge线程进行最后的删除。
更新主键
聚簇索引和二级索引都无法进行****in place update,都会产生两个版本update分两步执行,先删除该行,再插入一行目标行。
ReadView
看看ReadView,已提交读和可重复读的区别就在于它们生成ReadView的策略不同。
ReadView中主要就是有个列表来存储我们系统中当前活跃着的读写事务,也就是begin了还未提交的事务。通过这个列表来判断记录的某个版本是否对当前事务可见。假设当前列表里的事务id为[80,100]。
事务最小值:min_trx_id=12,事务最大值:max_trx_id=35,当前需要执行的事务id为12。
总体大概有三种场景
(1)如果你要访问的记录版本的事务id为50,比当前列表最小的id80小,那说明这个事务在之前就提交了,所以对当前活动的事务来说是可访问的。
(2)如果你要访问的记录版本的事务id为70,发现此事务在列表id最大值和最小值之间,那就再判断一下是否在列表内,如果在那就说明此事务还未提交,所以版本不能被访问。如果不在那说明事务已经提交,所以版本可以被访问。
(3)如果你要访问的记录版本的事务id为110,那比事务列表最大id100都大,那说明这个版本是在ReadView生成之后才发生的,所以不能被访问。
最后,这些记录都是去版本链里面找的,先找最近记录,如果最近这一条记录事务id不符合条件,不可见的话,再去找上一个版本再比较当前事务的id和这个版本事务id看能不能访问,以此类推直到返回可见的版本或者结束。
举个例子 ,在已提交读隔离级别下:
比如此时有一个事务id为100的事务,修改了name,使得的name等于小明2,但是事务还没提交。则此时的版本链:
比如此时有一个事务id为100的事务,修改了name,使得的name等于小明2,但是事务还没提交。则此时的版本链是那此时另一个事务发起了select语句要查询id为1的记录,那此时生成的ReadView列表只有[100]。那就去版本链去找了,首先肯定找最近的一条,发现trx_id是100,也就是name为小明2的那条记录,发现在列表内,所以不能访问。
这时候就通过指针继续找下一条,name为小明1的记录,发现trx_id是60,小于列表中的最小id,所以可以访问,直接访问结果为小明1。那这时候我们把事务id为100的事务提交了,并且新建了一个事务id为110也修改id为1的记录,并且不提交事务
这时候版本链就是
这时候之前那个select事务又执行了一次查询,要查询id为1的记录。
如果你是已提交读隔离级别,这时候你会重新一个ReadView,那你的活动事务列表中的值就变了,变成了[110]。
按照上的说法,你去版本链通过trx_id对比查找到合适的结果就是小明2。
如果你是可重复读隔离级别,这时候你的ReadView还是第一次select时候生成的ReadView,也就是列表的值还是[100]。所以select的结果是小明1。所以第二次select结果和第一次一样,所以叫可重复读!
****也就是说已提交读隔离级别下的事务在每次查询的开始都会生成一个独立的ReadView,而可重复读隔离级别则在第一次读的时候生成一个ReadView,之后的读都复用之前的ReadView。这就是Mysql的MVCC,通过版本链,实现多版本,可并发读-写,写-读。通过ReadView生成策略的不同实现不同的隔离级别。
以上是关于MySQL技术专题探究分析MySQL的MVCC原理介绍的主要内容,如果未能解决你的问题,请参考以下文章
MySQL专题 - 多版本并发控制 MVCC & read committed 隔离级别