MVCC简单地理解MVCC,全名是多版本控制,即同时控制多个版本。 MVCC是一种并发控制的方法,在数据库管理系统中一般实现对数据库的并发访问,在编程语言中实现事务内存。
这是百度百科的标准回答
转换为自己的语言:
多版本意味着数据库中同时存在多个版本的数据。 不是数据库整体的多个版本,而是同时存在某个记录的多个版本,某个事务处理对其进行操作时,查看该记录的隐藏列事务处理版本id,与事务处理id进行对照,实现事务处理的独立性
这些名词请记住。 如果能在博客上理解他们就okok了
事务隔离级别还原日志重做日志mvcc完整数据记录的好处作用
MySQL InnoDB中MVCC的实现主要是为了提高数据库的并发性,以便更好地处理读/写冲突,即使存在读/写冲突也不会锁定。 对于非块并行读取,已知并发访问数据库存在四个问题:脏写入(丢失修复)、脏读取、不可重复读取和幻读取。 MVCC通过最大限度地减少锁的使用来提高效率
隔离边界污染读取不可重复幻读READ UNCOMMITTED :可能发生未提交读取的READ COMMITTED :可能发生已提交读取解决方案的REPEATABLE READ :发生可重复读取解决方案
四个问题按严重性排序。 脏写脏读不能重复幻读
肮脏的书写这个问题太严重了,不管是什么隔离级别,都不允许肮脏的书写。
是的! 那么进入正题……
MVCC的实现原理数据库的四种隔离级别
我在以前的博客中谈到过InnoDB下的Compact行结构,有三个隐藏的列
列名是否必须描述row_ID行id,是否唯一标识记录,如果定义了主键,则没有。 transaction_id是事务IDroll_pointer,DB_ROLL_PTR是一个回滚指针,与还原日志配合使用,指向以前的版本http://ww
假设您首先添加了数据。 图:
两个事务同时更新信息,事务执行进程。
为什么两个事务的执行顺序有偏差?
很简单,如果可以同时交叉修正相同的数据,那不是“修正丢失(脏光)”的同时问题吗? mysql在执行操作时锁定,另一个事务暂时挂起
那么,更新了这么多次数据,他还是一个数据吗? 不,他在roll_pointer处记录了最近的更新记录,指向上一个更新数据:
每次更新此记录时,旧值都会保存在还原日志中。 即使是该记录的旧版本,随着更新次数的增加,所有版本都将通过roll_pointer属性连接到链表中。 这个链表称为版本链。 版本链中的第一个节点是当前记录的最新值。 此外,每个版本还包含生成该版本时对应的事务id
为什么没有脏写?
读视图是什么?
简而言之,读视图是当事务执行快照读取操作时生产的读取视图,当事务执行快照读取时数据库系统的当前快照每次事务开始时,都会分配一个ID,此ID会递增,从而导致最新事务的ID值较大。)
一、依赖于隐藏的两个列
您可以确定当前事务中显示的是版本链中的哪个版本。 他是怎么判断的呢? 详细介绍。
其中最重要的四个部分:
1、m _ ids :事务id列表,指示生成readview时当前系统上的活动读/写事务。
2、min _ Trx _ id :表示生成readview时当前系统中活动读写事务中最小的事务id,即m_ids中最小的事务id。
3、max _ Trx _ id :表示生成readview时系统应该分配给下一个事务的id值。
4、creator_trx_id :表示生成此ReadView的事务处理的事务处理id。
使用ReadView确定哪些版本的数据可读的过程:
如果被访问版本的trx_id属性值与ReadView的creator_trx_id值相同,则该版本可以通过当前事务访问,因为当前事务正在访问自己更改的记录如果被访问版本的trx_id属性值小于ReadView的min_trx_id值,则由于当前事务在生成ReadView之前已提交,因此生成版本的事务处理为当前事务处理如果被访问版本的trx_id属性值大于或等于ReadView的max_trx_id值,则访问权限将从当前事务生成ReadView后打开生成版本的事务如果访问的版本的trx_id属性值介于ReadView的min_trx_id和max_trx_id之间,则必须确定trx_id属性值
是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。但是对于READ COMMITTED(读取已提交) 和REPEATABLE READ(可重复读)两种隔离界别来说产生ReadViem是不同的,下面我们来就这两个隔离级别说一下,如何判断是否可读某个历史版本记录
READ COMMITTED(读取已提交)— 每次读取数据前都生成一个ReadView
下面过程一定认真看(我第一遍粗略看没有挨着读,结果什么也不明白,后来认真读了一遍,尽可能明白每一个步骤所描述的,看下来之后就会有恍然大明白的感觉!!!)
1、比方说现在系统里有两个事务id分别为100、200的事务在执行:
Transaction 100
BEGIN;
UPDATE hero SET name = ‘关羽’ WHERE number = 1;
UPDATE hero SET name = ‘lkdfk’ WHERE number = 1;
Transaction 200
BEGIN;
更新了一些别的表的记录 …
注意此时两个事务都没有进行提交
2、此刻,表hero中number为1的记录得到的版本链表如下所示:
3、假设现在有一个使用READ COMMITTED隔离级别的事务开始执行:
BEGIN;
SELECT1:Transaction 100、200未提交
SELECT * FROM hero WHERE number = 1;
得到的列name的值为’刘备’
那这个select的语句能都读取到的数据就是我们最关心的啦!
过程:
在执行SELECT语句时会先生成一个ReadView,ReadView的m_ids列表的内容就是[100,
200],min_trx_id为100,max_trx_id为201,creator_trx_id为0。
然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列name的内容是’lkdfk’,该版本的trx_id值为100,在m_ids列表内,所以不符合可见性要求,根据roll_pointer跳到下一个版本。
下一个版本的列name的内容是’关羽’,该版本的trx_id值也为100,也在m_ids列表内,所以也不符合要求,继续跳到下一个版本。
下一个版本的列name的内容是’刘备’,该版本的trx_id值为80,小于ReadView中的min_trx_id值100,所以这个版本是符合要求的,最后返回给用户的版本就是这条列name为’刘备’的记录。
4、之后,我们把事务id为100的事务提交一下
5、然后再到事务id为200的事务中更新一下表hero中number为1的记录(只有事务一执行完之后事务2才能执行,这个原因上面已经说过啦)
Transaction 200
BEGIN;
更新了一些别的表的记录 …
UPDATE hero SET name = ‘赵云’ WHERE number = 1;
UPDATE hero SET name = ‘axdtd’ WHERE number = 1;
此刻版本链是这样的:
6、然后再到刚才使用READ COMMITTED隔离级别的事务中继续查找这个number为1的记录(我们知道上面读取了一次,本次和上次的属于同一个事务不同次操作),如下
BEGIN;
SELECT1:Transaction 100、200均未提交
SELECT * FROM hero WHERE number = 1;
得到的列name的值为’刘备’
SELECT2:Transaction 100提交,Transaction 200未提交
SELECT * FROM hero WHERE
number = 1; # 得到的列name的值为’lkdfk’
7、这个SELECT2的执行过程如下:
在执行SELECT语句时会又会单独生成一个ReadView,该ReadView的m_ids列表的内容就是[200](事务id为100的那个事务已经提交了,所以再次生成快照时就没有它了),min_trx_id为200,max_trx_id为201,creator_trx_id为0。然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列name的内容是’axdtd’,该版本的trx_id值为200,在m_ids列表内,所以不符合可见性要求,根据roll_pointer跳到下一个版本。下一个版本的列name的内容是’赵云’,该版本的trx_id值为200,也在m_ids列表内,所以也不符合要求,继续跳到下一个版本。下一个版本的列name的内容是’lkdfk’,该版本的trx_id值为100,小于ReadView中的min_trx_id值200,所以这个版本是符合要求的,最后返回给用户的版本就是这条列name为’lkdfk’的记录。你认真看完了吗?如果明白了过程,你就记住这句话就行,以后遇到类似的场景就没有问题:
使用READ COMMITTED隔离级别的事务在每次查询开始时都会生成一个独立的ReadView。
REPEATABLE READ —— 在第一次读取数据时生成一个ReadView
我们还用上面的一样的场景看一下,进行对比,区别就显而易见啦
我们从事务100提交之后开始说(因为前面的操作是一样的,一样会读到“刘备”这一条数据,不过需要注意前面已经建立了一次ReadView)
1、然后再到事务id为200的事务中更新一下表hero中number为1的记录:
此时版本链是这样的:
2、然后再到刚才使用REPEATABLE READ隔离级别的事务中继续查找这个number为1的记录,如下:
使用REPEATABLE READ隔离级别的事务
BEGIN;
SELECT1:Transaction 100、200均未提交
SELECT * FROM hero WHERE number = 1;
得到的列name的值为’刘备’
SELECT2:Transaction 100提交,Transaction 200未提交
SELECT * FROM hero WHEREnumber = 1;
得到的列name的值仍为’刘备’
过程:
因为当前事务的隔离级别为REPEATABLE READ,而之前在执行SELECT1时已经生成过ReadView了,所以此时直接复用之前的ReadView,之前的ReadView的m_ids列表的内容就是[100, 200],min_trx_id为100,max_trx_id为201,creator_trx_id为0。
然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列name的内容是’axdtd’,该版本的trx_id值为200,在m_ids列表内,所以不符合可见性要求,根据roll_pointer跳到下一个版本。
下一个版本的列name的内容是’赵云’,该版本的trx_id值为200,也在m_ids列表内,所以也不符合要求,继续跳到下一个版本。
下一个版本的列name的内容是’lkdfk’,该版本的trx_id值为100,而m_ids列表中是包含值为100的事务id的,所以该版本也不符合要求,同理下一个列name的内容是’关羽’的版本也不符合要求。继续跳到下一个版本。
下一个版本的列name的内容是’刘备’,该版本的trx_id值为80,小于ReadView中的min_trx_id值100,所以这个版本是符合要求的,最后返回给用户的版本就是这条列c为’刘备’的记录。
也就是说两次SELECT查询得到的结果是重复的
下面个人的理解,如有错误可以指正:
ReadView可以查到又名“快照读”,每次执行“快照读”,就好像给数据库拍了一个照片,你拍到了什么就可以读到什么,当然,如果你自己要在照片上画一个“小狗”,那这个“小狗”你也是可以看到了,但是如果你拍了一个杯子,在你拍完之后,别人往杯子里加了点水,你能在照片上看到吗?当然不能。ReadView中的m_ids事务id列表就是你拍照前在你面前跃跃欲试的人,所以你要记录好他们。所以就是利用事务id的先后顺序来判断能否读到某个版本信息。
上面信息和图片借鉴掘金小册《MySQL 是怎样运行的:从根儿上理解 MySQL》