转自:https://blog.csdn.net/Waves___/article/details/105295060

最近在学习MySQL中的MVCC,看了网上的各种版本,什么创建版本号、删除版本号,一开始看的时候,好像很对的样子,但实际上很多都是错误的。经过好几天的查阅对比,在几篇博客的帮助下,才算是觉得正确理解了MySQL中的MVCC。
        本文是对MVCC的一些总结,并找到相关源码佐证(talk is cheap,show me the code!网上错误的解释实在是太多了)。如果你刚接触MVCC,或者是被网上的各种解释弄得快要晕了,请坚持看下去,一定会对你有收获。

目录

1、MVCC概念

1.1、隐藏字段

1.2、Read View 结构(重点)

1.3、Undo log

2、记录行修改的具体流程

3、可见性比较算法

4、当前读和快照读

5、例子(帮助理解)


1、MVCC概念

        多版本控制(Multiversion Concurrency Control): 指的是一种提高并发的技术。最早的数据库系统,只有读读之间可以并发,读写,写读,写写都要阻塞。引入多版本之后,只有写写之间相互阻塞,其他三种操作都可以并行,这样大幅度提高了InnoDB的并发度。在内部实现中,InnoDB通过undo log保存每条数据的多个版本,并且能够找回数据历史版本提供给用户读,每个事务读到的数据版本可能是不一样的。在同一个事务中,用户只能看到该事务创建快照之前已经提交的修改和该事务本身做的修改。

 MVCC在 Read Committed 和 Repeatable Read两个隔离级别下工作。

 MySQL的InnoDB存储引擎默认事务隔离级别是RR(可重复读),是通过 "行级锁+MVCC"一起实现的,正常读的时候不加锁,写的时候加锁。而 MVCC 的实现依赖:隐藏字段、Read View、Undo log

1.1、隐藏字段

 InnoDB存储引擎在每行数据的后面添加了三个隐藏字段:

 1. DB_TRX_ID(6字节):表示最近一次对本记录行作修改(insert | update)的事务ID。至于delete操作,InnoDB认为是一个update操作,不过会更新一个另外的删除位,将行表示为deleted。并非真正删除。

 2. DB_ROLL_PTR(7字节):回滚指针,指向当前记录行的undo log信息

 3. DB_ROW_ID(6字节):随着新行插入而单调递增的行ID。理解:当表没有主键或唯一非空索引时,innodb就会使用这个行ID自动产生聚簇索引。如果表有主键或唯一非空索引,聚簇索引就不会包含这个行ID了。这个DB_ROW_ID跟MVCC关系不大

隐藏字段并不是什么创建版本、删除版本。官方文档:14.3 InnoDB Multi-Versioning

.

1.2、Read View 结构(重点)

其实Read View(读视图),跟快照、snapshot是一个概念。

Read View主要是用来做可见性判断的, 里面保存了“对本事务不可见的其他活跃事务”。

Read View 结构源码,其中包括几个变量,在网上这些变量的解释各种各样,下面我结合源码给出它们正确的解释。
                         
        ① low_limit_id目前出现过的最大的事务ID+1,即下一个将被分配的事务ID。源码 350行:
                         
            max_trx_id的定义如下,源码 628行,翻译过来就是“还未分配的最小事务ID”,也就是下一个将被分配的事务ID。(low_limit_id 并不是活跃事务列表中最大的事务ID)
                        

 up_limit_id活跃事务列表trx_ids中最小的事务ID,如果trx_ids为空,则up_limit_id 为 low_limit_id。源码 358行:
                        

因为trx_ids中的活跃事务号是逆序的,所以最后一个为最小活跃事务ID。(up_limit_id 并不是已提交的最大事务ID+1,后面的 例子2 会证明这是错误的)

③ trx_ids:Read View创建时其他未提交的活跃事务ID列表。意思就是创建Read View时,将当前未提交事务ID记录下来,后续即使它们修改了记录行的值,对于当前事务也是不可见的。
           注意:Read View中trx_ids的活跃事务,不包括当前事务自己和已提交的事务(正在内存中),源码 295行:
                        

④ creator_trx_id:当前创建事务的ID,是一个递增的编号,源码 345行 。(这个编号并不是DB_ROW_ID)
                         

1.3、Undo log

Undo log中存储的是老版本数据,当一个事务需要读取记录行时,如果当前记录行不可见,可以顺着undo log链找到满足其可见性条件的记录行版本。
        大多数对数据的变更操作包括 insert/update/delete,在InnoDB里,undo log分为如下两类:
        ①insert undo log : 事务对insert新记录时产生的undo log, 只在事务回滚时需要, 并且在事务提交后就可以立即丢弃。
        ②update undo log : 事务对记录进行delete和update操作时产生的undo log,不仅在事务回滚时需要,快照读也需要,只有当数据库所使用的快照中不涉及该日志记录,对应的回滚日志才会被purge线程删除。

Purge线程:为了实现InnoDB的MVCC机制,更新或者删除操作都只是设置一下旧记录的deleted_bit,并不真正将旧记录删除。
        为了节省磁盘空间,InnoDB有专门的purge线程来清理deleted_bit为true的记录。purge线程自己也维护了一个read view,如果某个记录的deleted_bit为true,并且DB_TRX_ID相对于purge线程的read view可见,那么这条记录一定是可以被安全清除的。

2、记录行修改的具体流程

假设有一条记录行如下,字段有Name和Honor,值分别为"curry"和"mvp",最新修改这条记录的事务ID为1。

(1)现在事务A(事务ID为2)对该记录的Honor做出了修改,将Honor改为"fmvp":

①事务A先对该行加排它锁
                ②然后把该行数据拷贝到undo log中,作为旧版本
                ③拷贝完毕后,修改该行的Honor为"fmvp",并且修改DB_TRX_ID为2(事务A的ID), 回滚指针指向拷贝到undo log的旧版本。(然后还会将修改后的最新数据写入redo log)
                ④事务提交,释放排他锁
 

(2) 接着事务B(事务ID为3)修改同一个记录行,将Name修改为"iguodala":

①事务B先对该行加排它锁
                ②然后把该行数据拷贝到undo log中,作为旧版本
                ③拷贝完毕后,修改该行Name为"iguodala",并且修改DB_TRX_ID为3(事务B的ID), 回滚指针指向拷贝到undo log最新的旧版本。
                ④事务提交,释放排他锁

从上面可以看出,不同事务或者相同事务的对同一记录行的修改,会使该记录行的undo log成为一条链表,undo log的链首就是最新的旧记录,链尾就是最早的旧记录。

3、可见性比较算法

在innodb中,创建一个新事务后,执行第一个select语句的时候,innodb会创建一个快照(read view),快照中会保存系统当前不应该被本事务看到的其他活跃事务id列表(即trx_ids)。当用户在这个事务中要读取某个记录行的时候,innodb会将该记录行的DB_TRX_ID与该Read View中的一些变量进行比较,判断是否满足可见性条件。

假设当前事务要读取某一个记录行,该记录行的DB_TRX_ID(即最新修改该行的事务ID)为trx_id,Read View的活跃事务列表trx_ids中最早的事务ID为up_limit_id,将在生成这个Read Vew时系统出现过的最大的事务ID+1记为low_limit_id(即还未分配的事务ID)。

具体的比较算法如下(可以照着后面的 例子 ,看这段):

1. 如果 trx_id < up_limit_id, 那么表明“最新修改该行的事务”在“当前事务”创建快照之前就提交了,所以该记录行的值对当前事务是可见的。跳到步骤5。

2. 如果 trx_id >= low_limit_id, 那么表明“最新修改该行的事务”在“当前事务”创建快照之后才修改该行,所以该记录行的值对当前事务不可见。跳到步骤4。

3. 如果 up_limit_id <= trx_id < low_limit_id, 表明“最新修改该行的事务”在“当前事务”创建快照的时候可能处于“活动状态”或者“已提交状态”;所以就要对活跃事务列表trx_ids进行查找(源码中是用的二分查找,因为是有序的):

(1) 如果在活跃事务列表trx_ids中能找到 id 为 trx_id 的事务,表明①在“当前事务”创建快照前,“该记录行的值”被“id为trx_id的事务”修改了,但没有提交;或者②在“当前事务”创建快照后,“该记录行的值”被“id为trx_id的事务”修改了(不管有无提交);这些情况下,这个记录行的值对当前事务都是不可见的,跳到步骤4;

(2)在活跃事务列表中找不到,则表明“id为trx_id的事务”在修改“该记录行的值”后,在“当前事务”创建快照前就已经提交了,所以记录行对当前事务可见,跳到步骤5。

4. 在该记录行的 DB_ROLL_PTR 指针所指向的undo log回滚段中,取出最新的的旧事务号DB_TRX_ID, 将它赋给trx_id,然后跳到步骤1重新开始判断。

5. 将该可见行的值返回。

比较算法源码 84行,也可看下图,有注释,图代码来自 link:
         

4、当前读和快照读

快照读(snapshot read):普通的 select 语句(不包括 select ... lock in share mode, select ... for update)

当前读(current read) :select ... lock in share mode,select ... for update,insert,update,delete 语句(这些语句获取的是数据库中的最新数据,官方文档:14.7.2.4 Locking Reads )

只靠 MVCC 实现RR隔离级别,可以保证可重复读,还能防止部分幻读,但并不是完全防止。

比如事务A开始后,执行普通select语句,创建了快照;之后事务B执行insert语句;然后事务A再执行普通select语句,得到的还是之前B没有insert过的数据,因为这时候A读的数据是符合快照可见性条件的数据。这就防止了部分幻读,此时事务A是快照读

但是,如果事务A执行的不是普通select语句,而是select ... for update等语句,这时候,事务A是当前读,每次语句执行的时候都是获取的最新数据。也就是说,在只有MVCC时,A先执行 select ... where nid between 1 and 10 … for update;然后事务B再执行  insert … nid = 5 …;然后 A 再执行 select ... where nid between 1 and 10 … for update,就会发现,多了一条B insert进去的记录。这就产生幻读了,所以单独靠MVCC并不能完全防止幻读。

因此,InnoDB在实现RR隔离级别时,不仅使用了MVCC,还会对“当前读语句”读取的记录行加记录锁(record lock)和间隙锁(gap lock),禁止其他事务在间隙间插入记录行,来防止幻读。也就是前文说的"行级锁+MVCC"。

如果你对这些锁不是很熟悉,这是一篇将MySQL 中锁机制讲的很详细的博客 。

RR和RC的Read View产生区别:

①在innodb中的Repeatable Read级别, 只有事务在begin之后,执行第一条select(读操作)时, 才会创建一个快照(read view),将当前系统中活跃的其他事务记录起来;并且事务之后都是使用的这个快照,不会重新创建,直到事务结束。

②在innodb中的Read Committed级别, 事务在begin之后,执行每条select(读操作)语句时,快照会被重置,即会重新创建一个快照(read view)。

官方文档:consistent read,里面所说的consistent read 一致性读,我的理解就是 快照读,也就是普通select语句,它们不会对访问的数据加锁。     只有普通select语句才会创建快照,select ... lock in share mode,select ... for update不会,update、delete、insert语句也不会,因为它们都是 当前读,会对访问的数据加锁。

5、例子(帮助理解)

假设原始数据行:
Field DB_ROW_ID DB_TRX_ID DB_ROLL_PTR
0 10 10000 0x13525342

例子1

例子2

(证明“up_limit_id为已提交的最大事务ID + 1”是错误的)

例子3

(跟例子2一样的情况,不过up_limit_id变为trx_ids中最小的事务ID):

参考:
MySQL-InnoDB-MVCC多版本并发控制
MySQL数据库事务各隔离级别加锁情况--read committed && MVCC
InnoDB存储引擎MVCC的工作原理
【MySQL笔记】正确的理解MySQL的MVCC及实现原理
Mysql Innodb中undo-log和MVCC多版本一致性读 的实现
InnoDB事务分析-MVCC

MySQL中MVCC+行级锁的工作机制(源码佐证)相关推荐

  1. MySQL中的行级锁,表级锁,页级锁

    数据库锁是数据库系统中非常重要的一个概念,本文将深入分析数据库中的锁相关知识,您可点击下方音频收听或直接查看文稿中的文字. 在计算机科学中,锁是在执行多线程时用于强行限制资源访问的同步机制,即用于在并 ...

  2. Mysql中的行级锁、表级锁、页级锁

    转载自 Mysql中的行级锁.表级锁.页级锁 在计算机科学中,锁是在执行多线程时用于强行限制资源访问的同步机制,即用于在并发控制中保证对互斥要求的满足. 在数据库的锁机制中介绍过,在DBMS中,可以按 ...

  3. mysql数据库的行级锁有几种_MySQL中的行级锁、表级锁、页级锁

    在计算机科学中,锁是在执行多线程时用于强行限制资源访问的同步机制,即用于在并发控制中保证对互斥要求的满足. 在DBMS中,可以按照锁的粒度把数据库锁分为行级锁(INNODB引擎).表级锁(MYISAM ...

  4. MySQL数据库事务中的行级锁,表级锁,页级锁

    锁定用于确保事务完整性和数据库一致性. 锁定可以防止用户读取其他用户正在更改的数据,并防止多个用户同时更改相同的数据. 如果不使用锁定,数据库中的数据可能在逻辑上变得不正确,而针对这些数据进行查询可能 ...

  5. 【MySQL】InnoDB中的行级锁

    行锁,也称为记录锁,顾名思义就是在记录上加的锁.但是要注意,这个记录指的是通过给索引上的索引项加锁.InnoDB 这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,In ...

  6. mysql数据库的行级锁有几种_数据库行级锁和表锁区别

    MyISAM和InnoDB MyISAM MyISAM使用B+tree作为索引结构,叶节点存放的是数据地址. MyISAM不支持事务和外键. MyISAM是表锁,对数据库写操作时会锁住整个表,效率低. ...

  7. SQL Server 中 ROWLOCK 行级锁

    一.ROWLOCK的使用 1.ROWLOCK行级锁确保,在用户取得被更新的行,到该行进行更新,这段时间内不被其它用户所修改.因而行级锁即可保证数据的一致性,又能提高数据操作的并发性. 2.ROWLOC ...

  8. MySQL高级【行级锁】

    1:行级锁 1.1:介绍 行级锁,每次操作锁住对应的行数据.锁定粒度最小,发生锁冲突的概率最低,并发度最高.应用在 InnoDB存储引擎中. InnoDB的数据是基于索引组织的,行锁是通过对索引上的索 ...

  9. mysql数据库之行级锁

    行级锁,每次操作锁住对应的行数据,锁定粒度最小,发生锁冲突的概率最低,并发度最高.英英在innodb存储引擎中. innodb的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记 ...

最新文章

  1. mysql 一键获取数据库表结构
  2. 面试后总是没有结果的7大原因
  3. 在Linux上安装PostgreSQL
  4. 大逃杀计算机内存不足,绝地求生大逃杀内存不足怎么办 内存优化教程
  5. 【CyberSecurityLearning 57】XSS
  6. 解决org.springframework.web.multipart.MaxUploadSizeExceededException报错问题
  7. ShowSlow+Yslow环境搭建
  8. Google 强迫微软弃用 Edge 内核?
  9. java mysql查询试题_2016年Java认证考试题
  10. 逐步完善自己的3D引擎
  11. 黑苹果 efi如何替换_小白第一次安装黑苹果,然后卡代码,别着急,教你一个通用步骤,一个一个排查,大部分是可以解决的,毕竟安装不是最难的一个步骤...
  12. 软考初级程序员---题目(二)
  13. 当series用math包里的log计算出现错误
  14. 六级听力技巧与备考策略
  15. C语言的进制转换以及算法实现
  16. openlayers官方教程(三)Basics——Zooming to your location
  17. 进程和线程的区别 及 进程间通信的方式
  18. 三个参数 matlab程序,由XYZ三刺激值,得到Lab值(matlab程序)
  19. SAP推出On-Demand创新软件 瞄准中型企业
  20. 乌班图mysql的安装

热门文章

  1. JavaScript正则表达式的坑很深
  2. Idea 创建 web.xml 文件
  3. mybatis中collection中的ofType=“String“时
  4. pythonclass全局变量_Python的变量(全局变量、局部变量、类变量和实例变量)
  5. ❤️万字总结八大排序:冒泡排序,选择排序,插入排序,堆排序,希尔排序,归并排序,计数排序❤️
  6. 斐波那契数列的Python实现
  7. 动画以及View绘制中的addview实战
  8. 构建springmvc+dubbo分布式平台-dubbo简介
  9. Python 模块学习
  10. Eclipse 编码区-保护色-快捷大全