| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 大数据 -> 【Mysql高级篇】MVCC的原理 -> 正文阅读 |
|
[大数据]【Mysql高级篇】MVCC的原理 |
MVCC的原理MVCC原理前言本文摘自: Mysql是怎样运行的:从根上理解Mysql xiaolincoding.com 版本链我们前边说过,对于使用 InnoDB 存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列( row_id 并 不是必要的,我们创建的表中有主键或者非NULL的UNIQUE键时都不会包含 row_id 列):
比方说我们的表 hero 现在只包含一条记录:
假设插入该记录的 事务id 为 80 ,那么此刻该条记录的示意图如下所示: 假设之后两个 事务id 分别为 100 、 200 的事务对这条记录进行 UPDATE 操作,操作流程如下: 每次对记录进行改动,都会记录一条 undo日志 ,每条 undo日志 也都有一个 roll_pointer 属性( INSERT 操作 对应的 undo日志 没有该属性,因为该记录并没有更早的版本),可以将这些 undo日志 都连起来,串成一个链表,所以现在的情况就像下图一样: 对该记录每次更新后,都会将旧值放到一条 undo日志 中,就算是该记录的一个旧版本,随着更新次数的增多, 所有的版本都会被 roll_pointer 属性连接成一个链表,我们把这个链表称之为 ReadView对于使用
有了这个 ReadView ,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见:
如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断 可见性,依此类推,直到版本链中的最后一个版本。如果最后一个版本也不可见的话,那么就意味着该条记录对 该事务完全不可见,查询结果就不包含该记录。 在 MySQL 中, READ COMMITTED 和 REPEATABLE READ 隔离级别的的一个非常大的区别就是它们生成ReadView的 时机不同。我们还是以表 hero 为例来,假设现在表 hero 中只有一条由 事务id 为 80 的事务插入的一条记录:
接下来看一下 READ COMMITTED 和 REPEATABLE READ 所谓的生成ReadView的时机不同到底不同在哪里. READ COMMITTED —— 每次读取数据前都生成一个ReadView比方说现在系统里有两个 事务id 分别为 100 、 200 的事务在执行:
此刻,表 hero 中 number 为 1 的记录得到的版本链表如下所示: 假设现在有一个使用 READ COMMITTED 隔离级别的事务开始执行:
这个 SELECT1 的执行过程如下:
之后,我们把 事务id 为 100 的事务提交一下,就像这样:
然后再到 事务id 为 200 的事务中更新一下表 hero 中 number 为 1 的记录:
此刻,表 hero 中 number 为 1 的记录的版本链就长这样: 然后再到刚才使用 READ COMMITTED 隔离级别的事务中继续查找这个 number 为 1 的记录,如下:
以此类推,如果之后 事务id 为 200 的记录也提交了,再此在使用 READ COMMITTED 隔离级别的事务中查询表 hero 中 number 值为 1 的记录时,得到的结果就是 ‘诸葛亮’ 了,具体流程我们就不分析了。总结一下就是:使 用READ COMMITTED隔离级别的事务在每次查询开始时都会生成一个独立的ReadView。 REPEATABLE READ —— 在第一次读取数据时生成一个ReadView对于使用 REPEATABLE READ 隔离级别的事务来说,只会在第一次执行查询语句时生成一个 ReadView ,之后的查 询就不会重复生成了。我们还是用例子看一下是什么效果。 比方说现在系统里有两个 事务id 分别为 100 、 200 的事务在执行
此刻,表 hero 中 number 为 1 的记录得到的版本链表如下所示: 假设现在有一个使用 REPEATABLE READ 隔离级别的事务开始执行:
这个 SELECT1 的执行过程如下:
之后,我们把 事务id 为 100 的事务提交一下,就像这样:
然后再到 事务id 为 200 的事务中更新一下表 hero 中 number 为 1 的记录:
此刻,表 hero 中 number 为 1 的记录的版本链就长这样: 然后再到刚才使用 REPEATABLE READ 隔离级别的事务中继续查找这个 number 为 1 的记录,如下:
这个 SELECT2 的执行过程如下:
也就是说两次 SELECT 查询得到的结果是重复的,记录的列 c 值都是 ‘刘备’ ,这就是 可重复读 的含义。如果我 们之后再把 事务id 为 200 的记录提交了,然后再到刚才使用 REPEATABLE READ 隔离级别的事务中继续查找这 个 number 为 1 的记录,得到的结果还是 ‘刘备’ 可重复读是如何工作的?为了更好的理解MVCC,我们通过图解来加深印象 可重复读隔离级别是启动事务时生成一个 Read View,然后整个事务期间都在用这个 Read View。 假设事务 A (事务 id 为51)启动后,紧接着事务 B (事务 id 为52)也启动了,那这两个事务创建的 Read View 如下: 事务 A 和 事务 B 的 Read View 具体内容如下:
接着,在可重复读隔离级别下,事务 A 和事务 B 按顺序执行了以下操作:
接下来,跟大家具体分析下。 事务 B 第一次读小林的账户余额记录,在找到记录后,它会先看这条记录的 trx_id,此时发现 trx_id 为 50,比事务 B 的 Read View 中的 min_trx_id 值(51)还小,这意味着修改这条记录的事务早就在事务 B 启动前提交过了,所以该版本的记录对事务 B 可见的,也就是事务 B 可以获取到这条记录。 接着,事务 A 通过 update 语句将这条记录修改了(还未提交事务),将小林的余额改成 200 万,这时 MySQL 会记录相应的 undo log,并以链表的方式串联起来,形成版本链,如下图: 然后事务 B 第二次去读取该记录,发现这条记录的 trx_id 值为 51,在事务 B 的 Read View 的 min_trx_id 和 max_trx_id 之间,则需要判断 trx_id 值是否在 m_ids 范围内,判断的结果是在的,那么说明这条记录是被还未提交的事务修改的,这时事务 B 并不会读取这个版本的记录。而是沿着 undo log 链条往下找旧版本的记录,直到找到 trx_id 「小于」事务 B 的 Read View 中的 min_trx_id 值的第一条记录,所以事务 B 能读取到的是 trx_id 为 50 的记录,也就是小林余额是 100 万的这条记录。 最后,当事物 A 提交事务后,由于隔离级别时「可重复读」,所以事务 B 再次读取记录时,还是基于启动事务时创建的 Read View 来判断当前版本的记录是否可见。所以,即使事物 A 将小林余额修改为 200 万并提交了事务, 事务 B 第三次读取记录时,读到的记录都是小林余额是 100 万的这条记录。 就是通过这样的方式实现了,「可重复读」隔离级别下在事务期间读到的记录都是事务启动前的记录。 读提交是如何工作的?读提交隔离级别是在每次读取数据时,都会生成一个新的 Read View。 也意味着,事务期间的多次读取同一条数据,前后两次读的数据可能会出现不一致,因为可能这期间另外一个事务修改了该记录,并提交了事务。 那读提交隔离级别是怎么工作呢?我们还是以前面的例子来聊聊。 假设事务 A (事务 id 为51)启动后,紧接着事务 B (事务 id 为52)也启动了,接着按顺序执行了以下操作:
那具体怎么做到的呢?我们重点看事务 B 每次读取数据时创建的 Read View。前两次 事务 B 读取数据时创建的 Read View 如下图: 我们来分析下为什么事务 B 第二次读数据时,读不到事务 A (还未提交事务)修改的数据? 事务 B 在找到小林这条记录时,会看这条记录的 trx_id 是 51,在事务 B 的 Read View 的 min_trx_id 和 max_trx_id 之间,接下来需要判断 trx_id 值是否在 m_ids 范围内,判断的结果是在的,那么说明这条记录是被还未提交的事务修改的,这时事务 B 并不会读取这个版本的记录。而是,沿着 undo log 链条往下找旧版本的记录,直到找到 trx_id 「小于」事务 B 的 Read View 中的 min_trx_id 值的第一条记录,所以事务 B 能读取到的是 trx_id 为 50 的记录,也就是小林余额是 100 万的这条记录。 我们来分析下为什么事务 A 提交后,事务 B 就可以读到事务 A 修改的数据? 在事务 A 提交后,由于隔离级别是「读提交」,所以事务 B 在每次读数据的时候,会重新创建 Read View,此时事务 B 第三次读取数据时创建的 Read View 如下: 事务 B 在找到小林这条记录时,会发现这条记录的 trx_id 是 51,比事务 B 的 Read View 中的 min_trx_id 值(52)还小,这意味着修改这条记录的事务早就在创建 Read View 前提交过了,所以该版本的记录对事务 B 是可见的。 正是因为在读提交隔离级别下,事务每次读数据时都重新创建 Read View,那么在事务期间的多次读取同一条数据,前后两次读的数据可能会出现不一致,因为可能这期间另外一个事务修改了该记录,并提交了事务。 总结事务是在 MySQL 引擎层实现的,我们常见的 InnoDB 引擎是支持事务的,事务的四大特性是原子性、一致性、隔离性、持久性,我们这次主要讲的是隔离性。 当多个事务并发执行的时候,会引发脏读、不可重复读、幻读这些问题,那为了避免这些问题,SQL 提出了四种隔离级别,分别是读未提交、读已提交、可重复读、串行化,从左往右隔离级别顺序递增,隔离级别越高,意味着性能越差,InnoDB 引擎的默认隔离级别是可重复读。 要解决脏读现象,就要将隔离级别升级到读已提交以上的隔离级别,要解决不可重复读现象,就要将隔离级别升级到可重复读以上的隔离级别。 而对于幻读现象,不建议将隔离级别升级为串行化,因为这会导致数据库并发时性能很差。MySQL InnoDB 引擎的默认隔离级别虽然是「可重复读」,但是它很大程度上避免幻读现象(并不是完全解决了,详见这篇文章 (opens new window)),解决的方案有两种:
对于「读提交」和「可重复读」隔离级别的事务来说,它们是通过 Read View 来实现的,它们的区别在于创建 Read View 的时机不同:
这两个隔离级别实现是通过「事务的 Read View 里的字段」和「记录中的两个隐藏列」的比对,来控制并发事务访问同一个记录时的行为,这就叫 MVCC(多版本并发控制)。 在可重复读隔离级别中,普通的 select 语句就是基于 MVCC 实现的快照读,也就是不会加锁的。而 select … for update 语句就不是快照读了,而是当前读了,也就是每次读都是拿到最新版本的数据,但是它会对读到的记录加上 next-key lock 锁。 思维导图 |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/26 10:33:04- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |