原文网址:MySQL原理--隔离级别的实现方式--MVCC_IT利刃出鞘的博客-CSDN博客

简介

本文介绍MySQL的隔离级别的实现方式--MVCC。

这也是Java后端面试题中常见的一个问题。

MVCC简介

MVCC(Multi-Version Concurrency Control),含义为:多版本并发控制。

在并发访问的时候,数据存在版本的概念,可以有效地提升数据库并发能力,常见的数据库如MySQL、MS SQL Server、IBM DB2、Hbase、MongoDB等等都在使用。

简单讲,如果没有MVCC,当想要读取的数据被其他事务用排它锁锁住时,只能互斥等待;而MVCC可以通过提供历史版本从而能够读取被锁的数据(的历史版本),避免了互斥等待。

在 MySQL中,MVCC是 InnoDB 存储引擎实现隔离级别的一种具体方式。

  • 未提交读:无需使用 MVCC(总是读取最新的数据行)
  • 提交读可重复读:使用MVCC来实现。
  • 可串行化:需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。

MVCC一般有两种实现方式(本文所讲的InnoDB采用的是后者)

  1. 实时保留数据的一个或多个历史版本
  2. 在需要时通过undo log构造出历史版本

快照读与当前读

快照读

什么时候是快照读?

单纯SELECT 操作,不包括:

  1. SELECT ... LOCK IN SHARE MODE (共享读锁)
  2. SELECT ... FOR UPDATE

提交读和可重复读的快照读

  • 提交读

    • 每次SELECT都生成一个快照读。
  • 可重复读
    • 开启事务后,第一个SELECT语句会快照读(对整个库拍了个快照)(不是一开启事务就快照读)。
    • 与可重复读的含义对应:一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。

实现方式(MVCC)

下图右侧绿色的是数据:一行数据记录,主键ID是10,name='Jack',age=10,  被update更新set为name= 'Tom',age=23。

  事务会先使用“排他锁”锁定该行,将该行当前的值复制到undo log中,然后再真正地修改当前行的值,最后填写事务的DB_TRX_ID,使用回滚指针DB_ROLL_PTR指向undo log中修改前的行DB_ROW_ID

  DB_TRX_ID: 6字节 DB_TRX_ID 字段,表示最后更新的事务id(update,delete,insert)。此外,删除在内部被视为更新,其中行中的特殊位被设置为将其标记为已软删除。

  DB_ROLL_PTR: 7字节回滚指针,指向前一个版本的undolog记录,组成undo链表。如果更新了行,则撤消日志记录包含在更新行之前重建行内容所需的信息。
  DB_ROW_ID: 6字节的DB_ROW_ID字段,包含一个随着新行插入而单调递增的行ID, 当由innodb自动产生聚集索引时,聚集索引会包括这个行ID的值,否则这个行ID不会出现在任何索引中。如果表中没有主键或合适的唯一索引, 也就是无法生成聚簇索引的时候, InnoDB会帮我们自动生成聚集索引, 聚簇索引会使用DB_ROW_ID的值来作为主键; 如果表中有主键或者合适的唯一索引, 那么聚簇索引中也就不会包含 DB_ROW_ID了 。 

  其它:insert undo log只在事务回滚时需要, 事务提交就可以删掉了。update undo log包括update 和 delete , 回滚和快照读 都需要。

当前读

什么时候是当前读?

  1. SELECT ... LOCK IN SHARE MODE (共享读锁)
  2. SELECT ... FOR UPDATE
  3. INSERT,UPDATE,DELETE

简介

当前读读取的是最新版本, 并且对读取的记录加锁,阻塞其他事务同时改动相同记录,避免出现安全问题

例如,假设要update一条记录,但是另一个事务已经delete这条数据并且commit了,如果不加锁就会产生冲突。所以update的时候肯定要是当前读,得到最新的信息并且锁定相应的记录。

实现方式(next-key锁 ( 行记录锁+Gap间隙锁 ))

        间隙锁:只在Read Repeatable、Serializable隔离级别才有,锁定范围空间的数据。假设id有3,4,5,锁定id > 3的数据,是指的4,5及后面的数字都会被锁定,因为此时若不锁定没有的数据,例如当加入了新的数据id=6,就会出现幻读,间隙锁避免了幻读。

  1. 没有索引的列

    1. 当前读操作时,会加全表gap锁,生产环境要注意。
  2. 主键或唯一索引
    1. 如果当前读时,where条件全部精确命中(=或者in),这种场景本身就不会出现幻读,所以只会加行记录锁。
  3. 非唯一索引列
    1. 如果where条件部分命中(>、<、like等)或者全未命中,则会加附近Gap间隙锁。
    2. 例如,某表数据如下,非唯一索引2, 6, 9, 9, 11, 15。如下语句要操作非唯一索引列9的数据,gap锁将会锁定的列是(6,11],该区间内无法插入数据。

原理

说明

        事务ID是在MySQL开启事务时为其分配的递增序列号,由于是递增的,所以可以基于此判断事务先后关系。

MVCC的多版本指的是针对数据库中的一行数据,都可能通过undo log中的数据算出多条行数据,每行数据版本不同(是为多版本),针对每次写操作,事务提交前,都会在undo log中记录相应的变动以及对应的事务ID,再结合数据表中的当前行数据,就可以回溯出一个行的的多个版本了。

Innodb会为每行数据添加两个字段 up_txid、del_txid,分别是更新事务ID、删除事务ID,事务新增或者更新一个数据行后,会将该事务ID记录在该行数据的up_txid中,事务删除行数据后,会将该事务ID记录在del_txid中。

在read repeatable隔离级别下

该隔离级别下的事务启动时,除了分配上面说的事务ID外,系统还会查出当前活跃的事务ID列表(也就是开启了但还未提交的事务),分配给该事务存储下来,有了这些信息,就可以实现快照读了,RR隔离级别下,其查询到的行数据需要满足:

  1. 行数据的up_txid <= 当前事务ID,并且不在活跃事务ID列表中
  2. 行数据的del_txid为null,或者 > 当前事务ID,或者在活跃事务ID列表中

简单理解下,只查询在当前事务开启之前就已经提交的数据,并且这行数据未被删除或者在当前事务开启后删除,相当于事务启动时,拍了个快照,事务执行期间,就通过这个快照读取数据,其他事务的变动不会再对当前事务产生影响,这就是:可重复读

在读取时,会从最新的一条数据开始读起,如果满足条件就以其为准,如果不满足就找到更旧的一行数据继续判断。

read committed隔离级别下

和RR隔离级别一样的是,RC隔离级别下的查询也是快照读,区别就是RC隔离级别下每次SELECT时都会获取下当前活跃事务ID列表,然后从最新一行数据开始,判断是否满足如下条件,不满足则继续判断更旧的一行数据:

  1. 行数据的up_txid不在活跃事务ID列表中(表示已经提交)
  2. 行数据的del_txid为null,或者在活跃事务ID列表中未提交

也就是:每次都读取当前已经提交的并且未被删除的最新数据,相当于每次查询都会拍个快照。

当前读

如果查询加了锁,就不在MVCC的控制范畴了,因为此时用的是当前读 。当前读的规则,就是要能读到所有已经提交的记录的最新值。当前读是由锁来保证的。Innodb中有行锁,上面举例的几条语句,都会锁住id=1的这行数据,这样其他事务如果要对id=1这行数据进行当前读,只能等行锁释放,等到啥时候?事务完成的时候会释放掉锁,既然事务都完成了,那其他事务自然能读取到已提交的最新值。

MVCC

原理简述

简介

在MySQL中,Innodb存储引擎支持MVCC。

MVCC并发控制的执行过程

以UPDATE为例:BEGIN=> 用排他锁锁定该行=> 记录redo log=> 记录undo log=> 修改当前行的值,写事务编号。

  • SELECT

    • Innodb检查每行数据,确保他们符合两个标准(符合了下边两点则返回查询结果):

      • InnoDB只查找版本早于当前事务版本的数据行(也就是数据行的版本必须小于等于事务的版本),这确保当前事务读取的行都是事务之前已经存在的,或者是由当前事务创建或修改的行。
      • 行的删除操作的版本一定是未定义的或者大于当前事务的版本号,确定了当前事务开始之前,行没有被删除。
  • INSERT
    • InnoDB为每个新增行记录当前系统版本号作为创建ID。“创建时间”=DB_ROW_ID,这时,“删除时间 ”是未定义的。
  • DELETE
    • InnoDB为每个删除行的记录当前系统版本号作为行的删除ID。
  • UPDATE
    • InnoDB复制了一行。这个新行的版本号使用了系统版本号。它也把系统版本号作为了删除行的版本。

InnoDB中的相关数据

InnoDb的最基本的行中包含一些额外的存储信息:DATA_TRX_ID,DATA_ROLL_PTR,DB_ROW_ID,DELETE BITInnodb为每行记录都实现了三个隐藏字段:

  • 6字节的事务ID(DB_TRX_ID)。
    (该行所的事务id,每处理一个事务,其值自动+1。可以基于此判断事务先后关系)
  • 7字节的回滚指针(DB_ROLL_PTR)。
    (指向当前记录项的rollback segment的undo log记录,找之前版本的数据就是通过这个指针)
  • 6字节的隐式主键(DB_ROW_ID)。
    Innodb自动产生聚集索引时,聚集索引包括这个DB_ROW_ID的值,否则聚集索引中不包括这个值,这个用于索引当中。
  • 删除标识位(DELETE BIT)。
    用于标识该记录是否被删除,这里的不是真正的删除数据,而是标志出来的删除。真正意义的删除是在commit的时候

为了支持事务,Innbodb引入了下面几个概念:

  • redo log

    • redo log用于故障恢复。MySQL会将执行的SQL语句保存到一个指定的Log文件,当MySQL执行recovery时重新执行redo log记录的SQL操作即可。
    • 当客户端执行每条SQL(更新语句)时,redo log会被首先写入redo log buffer;当客户端执行COMMIT命令时,log buffer中的内容会被视情况刷新到磁盘。
    • redo log在磁盘上作为一个独立的文件存在,即Innodb的log文件。
  • undo log
    • undo log用于回滚。

      • MySQL会copy事务前的数据库内容(行)到undo buffer,在适合的时间把undo buffer中的内容刷新到磁盘。
      • undo buffer与redo buffer一样,也是环形缓冲,当缓冲满的时候,undo buffer中的内容会也会被刷新到磁盘;
    • 与redo log不同的是,磁盘上不存在单独的undo log文件,所有的undo log均存放在主ibd数据文件中(表空间),即使客户端设置了每表一个数据文件也是如此。
  • rollback segment
    • 回滚段这个概念来自Oracle的事物模型,在Innodb中,undo log被划分为多个段,具体某行的undo log就保存在某个段中,称为回滚段。
    • 可以认为undo log和回滚段是同一意思。
  • 锁(前边已有讲述)
  • 隔离级别(前边已有讲述)

实例

有事务插入persion表插入了一条新记录:name为Jerry,age为24。可认为:隐式ID是1;事务ID和回滚指针,为NULL。

事务1对该记录的name做出修改,改为Tom

当事务1更改该行的值时,会进行如下操作:

  1. 用排他锁锁定该行
  2. 把该行数据拷贝到undo log中,作为旧记录(即:在undo log中有当前行的拷贝副本)
  3. 拷贝完毕后,有如下操作:
    1. 修改该行name为Tom;
    2. 修改隐藏字段的事务ID为当前事务1的ID(我们默认从1开始,之后递增);
    3. 回滚指针指向拷贝到undo log的副本记录(即:我的上一个版本是它)。
  4. 事务提交后,释放锁

事务2修改person表的同一个记录,将age修改为30岁

当事务2更改该行的值时,会进行如下操作:

  1. 用排他锁锁定该行
  2. 把该行数据拷贝到undo log中,作为旧记录
    1. 发现该行记录已经有undo log了,那么最新的旧数据作为链表的表头,插在该行记录的undo log最前面
  3. 拷贝完毕后,有如下操作:
    1. 修改该行age为30岁;
    2. 修改隐藏字段的事务ID为当前事务2的ID,那就是2
    3. 回滚指针指向刚刚拷贝到undo log的副本记录
  4. 事务提交后,释放锁

从上面,我们就可以看出,不同事务或者相同事务的对同一记录的修改,会导致该记录的undo log成为一条记录版本线性表,即:事务链,undo log的链首就是最新的旧记录,链尾就是最早的旧记录。

因此,如果undo log一直不删除,则会通过当前记录的回滚指针回溯到该行创建时的初始内容。所幸的是,在Innodb中存在purge线程,它会查询那些比现在最老的活动事务还早的undo log,并删除它们,从而保证undo log文件不至于无限增长。

其他网址

正确的理解MySQL的MVCC及实现原理-12172612-51CTO博客
数据库MVCC 隔离级别_数据库_Jaylon Wang的专栏-CSDN博客

【MySQL】当前读、快照读、MVCC - wwcom123 - 博客园

MySQL是如何实现可重复读的? - InfoQ 写作平台
事务的可重复读的能力是怎么实现的? - Java学习指南

MySQL原理--隔离级别的实现方式--MVCC相关推荐

  1. mysql 默认事务隔离级别_一文读懂MySQL的事务隔离级别及MVCC机制

    回顾前文: <一文学会MySQL的explain工具> <一文读懂MySQL的索引结构及查询优化> (同时再次强调,这几篇关于MySQL的探究都是基于5.7版本,相关总结与结论 ...

  2. Mysql原理与实践学习总结

    数据库(MySQL) 本文根据自己对MySQL的学习和实践以及各类文章与书籍总结而来. 囊括了MySQL数据库的基本原理和技术.本文主要是我的一个学习总结,基于之前的系列文章做了一个概括,如有错误,还 ...

  3. 架构师技能6:深入MySQL原理-Waiting for table metadata lock引发系统崩溃

    开篇语录:以架构师的能力标准去分析每个问题,过后由表及里分析问题的本质,复盘总结经验,并把总结内容记录下来.当你解决各种各样的问题,也就积累了丰富的解决问题的经验,解决问题的能力也将自然得到极大的提升 ...

  4. 【运维面试】面试官: 你能说一下MySQL主从的几种方式吗?

    一般面试官问这个问题,都伴随着另外一个问题,叫:小伙子,你说一下MySQL主从原理? 当你回答完原理之后,就会问到关于MySQL主从的几种方式的问题. 一般我们的主从同步,分为:半同步复制和异步复制: ...

  5. Mysql原理篇之表空间---05

    Mysql原理篇之表空间---05 前言 回顾 页面类型 页面通用部分 独立表空间结构 区(extent)的概念 段(segment)的概念 区的分类 整理 XDES Entry链表 链表基节点 链表 ...

  6. Mysql原理解析 - 基本架构

    Mysql原理解析 - 基本架构 1.Mysql的基本架构图 1.1 连接器 1.2 查询缓存 1.3 分析器 1.4 优化器 1.5 存储引擎 1.Mysql的基本架构图 1.1 连接器 连接器负责 ...

  7. Mysql原理解析 - 索引文件的存储结构

    Mysql原理解析 - 索引文件的存储结构 前言 局部性原理 磁盘预读 索引是什么? 1. MSQL为什么索引选择B+树? 1.1 哈希表hash 简介: 局限性: 1.2 二叉树 简介: 局限性: ...

  8. Linux下MySQL的几种安装方式

    闲来有空,整理下Linux下Mysql的几种安装方式,分别使用yum/rpm.常规方式编译安装.cmake方式编译安装以及使用二进制方式免编译安装MySQL Linux系统环境: CentOS rel ...

  9. django 不用自带的mysql_21_django配置使用mysql数据库的两种方式

    配置django项目使用mysql数据库的两种方式 1. 直接在settings.py 文件中添加数据库配置信息 # 配置数据库的第一种方式 DATABASES = { 'default': { 'E ...

最新文章

  1. 区块链学堂(5):Geth 安装
  2. 在windows下用VMware虚拟机来安装linux
  3. python运行出现数据错误_Python运行出错情况
  4. MySQL必会企业面试题
  5. 漫步最优化二——基本优化问题
  6. 三星Galaxy Note 10系列价格曝光:顶配售价要破万
  7. idea debug异常关闭 Error running 'Tomcat8': Unable to open debugger port (127.0.0.1:50168): java.net.Soc
  8. xml存储数据 优缺点_Python大神都是这样处理xml文件的!
  9. idea部署启动ssm项目
  10. 北京2020积分落户名单
  11. 费马小定理和欧拉定理
  12. Chia 云P图 全套解决方案
  13. Harbor未授权创建管理员漏洞(CVE-2019-16097)
  14. Crumb -面包屑状的嵌套按钮
  15. embed的名词_embed是什么意思_embed的翻译_音标_读音_用法_例句_爱词霸在线词典
  16. 移动开发者大会第一日观感
  17. QQ音乐客户端Web页面通用性能优化实践
  18. Python的单行注释和多行注释的区别
  19. decode函数吗 jsp_decode 函数用法
  20. 【经验】tf1.x迁移到tf2.x教程

热门文章

  1. 带权重的多点滑动平均
  2. usg5150配虚拟服务器,UniFi - USG 内置 RADIUS 服务器配置步骤
  3. php中setcookie,php中setcookie() 函数用法介绍
  4. 我男朋友是个程序员# 3 之《备份引发的血案》
  5. 平面设计系统自学?攻略掌握值得收藏
  6. video标签样式属性设置
  7. 网络媒体十八种赢利模式
  8. 用压力传感器测量小米净水器及接管线机的改装方案
  9. DDPG(6)_ddpg
  10. Java IDE MyEclipse 使用教程:创建Web服务项目