MySQL 一致性视图(MVCC)
更多内容,前往IT-BLOG
MySQL中支持的四种隔离级别
提到事务,你肯定会想到 ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离性、持久性),来说说其中I,也就是“隔离性”。当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,所以下面我们来说说隔离级别。
SQL标准的事务隔离级别包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)、串行化(serializable)。
【1】读未提交是指,一个事务还没提交时,它做的变更就能被别的事务看到;
【2】读已提交指,一个事务提交之后,它做的变更才会被其他事务看到;
【3】可重复读指,一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据时一致的。当然可重复读隔离级别下,未提交变更对其他事务也是不可见的;
【4】串行化,顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行;
MySQL在 REPEATABLE READ隔离级别下,是可以禁止幻读问题的发生的。我们可以通过下面命令来设置隔离级别。
SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL [REPEATABLE READ|READ COMMITTED|READ UNCOMMITTED|SERIALIZABLE];
MVCC原理
对于使用 InnoDB存储引擎的表来说,它的聚簇索引记录中都包含必要的隐藏列:【trx_id:每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的事务id赋值给 trx_id隐藏列】
ReadView
ReadView所解决的问题是使用 READ COMMITTED和 REPEATABLE READ隔离级别的事务中,不能读到未提交的记录,这需要判断一下版本链中的哪个版本是当前事务可见的。ReadView中主要包含4个比较重要的内容:
【1】m_ids:表示在生成 ReadView时当前系统中活跃的读写事务的事务id列表。
【2】min_trx_id:表示在生成 ReadView时当前系统中活跃的读写事务中最小的事务id,也就是 m_ids中的最小值。
【3】max_trx_id:表示生成ReadView时系统中应该分配给下一个事务的 id值。
【4】creator_trx_id:表示生成该 ReadView事务的事务id。
ReadView是如何工作的?
有了这些信息,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见:
【1】如果被访问版本的 trx_id属性值与 ReadView中的 creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
【2】如果被访问版本的 trx_id属性值小于 ReadView中的min_trx_id值,表明生成该版本的事务在当前事务生成 ReadView前已经提交,所以该版本可以被当前事务访问。
【3】如果被访问版本的 trx_id属性值大于 ReadView中的max_trx_id值,表明生成该版本的事务在当前事务生成 ReadView后才开启,所以该版本不可以被当前事务访问。
【4】如果被访问版本的 trx_id属性值在 ReadView的 min_trx_id和 max_trx_id之间,那就需要判断一下 trx_id属性值是不是在 m_ids列表中,如果在,说明创建 ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView时生成该版本的事务已经被提交,该版本可以被访问。
如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后一个版本。如果最后一个版本也不可见的话,那么就意味着该条记录对该事务完全不可见,查询结果就不包含该记录。
在 MySQL中,READ COMMITTED 和 REPEATABLE READ隔离级别的的一个非常大的区别就是它们生成 ReadView的时机不同。
我们这里使用一个示例来解释:
1 CREATE TABLE `t` (
2 `id` int(11) NOT NULL,
3 `k` int(11) DEFAULT NULL,
4 PRIMARY KEY (`id`)
5 ) ENGINE=InnoDB;
6 insert into t(id, k) values(1,1) ;
| 事务A | 事务B |
|---|---|
| begin | |
| begin | |
| update t set k= k+1 where id=1; | |
| commit; | |
| update t set k = k+1 where id=1; | |
| select k from t where id =1; | |
| commit; |
在这个例子中,我们做如下假设:
【1】事务A、B的版本号分别是100、200,且当前系统里只有这3个事务;
【2】三个事务开始前,(1,1)这一行数据的 row trx_id是90;
READ COMMITTED
每次读取数据前都生成一个ReadView
继续上面的例子,假设现在有一个使用 READ COMMITTED隔离级别的事务开始执行:
1 # 使用READ COMMITTED隔离级别的事务
2 BEGIN;
3
4 # SELECT1:Transaction 100、200未提交
5 select k from t where id=1 ; # 得到值为1
这个 SELECT的执行过程如下:
【1】在执行 SELECT语句时会先生成一个ReadView,ReadView的 m_ids列表的内容就是[100, 200],min_trx_id为100,max_trx_id为201,creator_trx_id为0;
【2】然后从版本链中挑选可见的记录,最新的版本 trx_id值为200,在 m_ids列表内,所以不符合可见性要求;
【3】下一个版本的 trx_id值也为100,也在 m_ids列表内,所以也不符合要求,继续跳到下一个版本;
【4】下一个版本的 trx_id值为90,小于 ReadView中的 min_trx_id值100,所以这个版本是符合要求的;
之后,我们把事务B的事务提交一下,然后再到刚才使用 READ COMMITTED隔离级别的事务中继续查找,如下:
# 使用READ COMMITTED隔离级别的事务
BEGIN; # SELECT1:Transaction 100、200均未提交
SELECT * FROM hero WHERE number = 1; # 得到值为1 # SELECT2:Transaction 200提交,Transaction 100未提交
SELECT * FROM hero WHERE number = 1; # 得到值为2
这个 SELECT2的执行过程如下:
【1】在执行 SELECT语句时又会单独生成一个 ReadView,该 ReadView的 m_ids列表的内容就是[100](事务id为 200的那个事务已经提交了,所以再次生成快照时就没有它了),min_trx_id为100,max_trx_id为201,creator_trx_id为0;
【2】然后从版本链中挑选可见的记录,从图中可以看出,最新版本 trx_id值为100,在 m_ids列表内,所以不符合可见性要求;
【3】下一个版本的 trx_id值为200,小于max_trx_id,并且不在 m_ids列表中,所以可见,返回的值为2;
REPEATABLE READ
在第一次读取数据时生成一个ReadView
假设现在有一个使用 REPEATABLE READ隔离级别的事务开始执行:
1 # 使用REPEATABLE READ隔离级别的事务
2 BEGIN;
3
4 # SELECT1:Transaction 100、200未提交
5 SELECT * FROM hero WHERE number = 1; # 得到值为1
这个SELECT1的执行过程如下:
【1】在执行 SELECT语句时会先生成一个 ReadView,ReadView的 m_ids列表的内容就是[100, 200],min_trx_id为100,max_trx_id为201,creator_trx_id为0;
【2】然后从版本链中挑选可见的记录,该版本的 trx_id值为100,在m_ids列表内,所以不符合可见性要求;
【3】下一个版本该版本的 trx_id值为200,也在 m_ids列表内,所以也不符合要求,继续跳到下一个版本;
【4】下一个版本的 trx_id值为90,小于 ReadView中的 min_trx_id值100,所以这个版本是符合要求的;
之后,我们把事务B 的事务提交一下,然后再到刚才使用 REPEATABLE READ隔离级别的事务中继续查找:
1 # 使用REPEATABLE READ隔离级别的事务
2 BEGIN;
3
4 # SELECT1:Transaction 100、200均未提交
5 SELECT * FROM hero WHERE number = 1; # 得到值为1
6
7 # SELECT2:Transaction 200提交,Transaction 100未提交
8 SELECT * FROM hero WHERE number = 1; # 得到值为1
这个SELECT2的执行过程如下:
【1】因为当前事务的隔离级别为 REPEATABLE READ,而之前在执行 SELECT1时已经生成过 ReadView了,所以此时直接复用之前的ReadView,之前的 ReadView的 m_ids列表的内容就是[100, 200],min_trx_id为100,max_trx_id为201,creator_trx_id为0。
【2】然后从版本链中挑选可见的记录,该版本的 trx_id值为100,在 m_ids列表内,所以不符合可见性要求;
【3】下一个版本该版本的 trx_id值为200,也在 m_ids列表内,所以也不符合要求,继续跳到下一个版本;
【4】下一个版本的 trx_id值为90,小于 ReadView中的 min_trx_id值100,所以这个版本是符合要求的;
MySQL事务原子性保证
事务原子性要求事务中的一系列操作要么全部完成,要么不做任何操作,不能只做一半。原子性对于原子操作很容易实现,就像HBase中行级事务的原子性实现就比较简单。但对于多条语句组成的事务来说,如果事务执行过程中发生异常,需要保证原子性就只能回滚,回滚到事务开始前的状态,就像这个事务根本没有发生过一样。如何实现呢?
MySQL实现回滚操作完全依赖于undo log,多说一句,undo log在 MySQL除了用来实现原子性保证之外,还用来实现MVCC,下文也会涉及到。使用 undo实现原子性在操作任何数据之前,首先会将修改前的数据记录到 undo log中,再进行实际修改。如果出现异常需要回滚,系统可以利用 undo中的备份将数据恢复到事务开始之前的状态。下图是 MySQL中表示事务的基本数据结构,其中与 undo相关的字段为 insert_undo和 update_undo,分别指向本次事务中所产生的 undo log。

事务回归根据 update_undo(或者indert_undo)找到对应的 undo log,做逆向操作即可。对于已经标记删除的数据清理删除标记,对于更新数据直接回滚更新;插入操作稍微复杂一些,不仅需要删除数据,还需要删除相关的聚集索引以及更新二级索引记录。
MySQL 一致性视图(MVCC)的更多相关文章
- MySQL——一致性非锁定读(快照读)&MVCC
MySQL--一致性非锁定读(快照读) MySQL数据库中读分为一致性非锁定读.一致性锁定读 一致性非锁定读(快照读),普通的SELECT,通过多版本并发控制(MVCC)实现. 一致性锁定读(当前读) ...
- 温故知新-Mysql锁&事务&MVCC
文章目录 锁概述 锁分类 MyISAM 表锁 InnoDB 行锁 事务及其ACID属性 InnoDB 的行锁模式 注意 MVCC InnoDB 中的 MVCC 参考 你的鼓励也是我创作的动力 Post ...
- MySQL锁与MVCC
--MySQL锁与MVCC --------------------2014/06/29 myisam表锁比较简单,这里主要讨论一下innodb的锁相关问题. innodb相比oracle锁机制简单许 ...
- 第五章 MySQL事务,视图,索引,备份和恢复
第五章 MySQL事务,视图,索引,备份和恢复 一.事务 1.什么是事务 事务是一种机制,一个操作序列,它包含了一组数据库操作命令,并且把所有的命令作为一个整体一起向系统提交或撤销操作请求.要么都执行 ...
- 数据库MySQL之 视图、触发器、存储过程、函数、事务、数据库锁、数据库备份、事件
数据库MySQL之 视图.触发器.存储过程.函数.事务.数据库锁.数据库备份.事件 浏览目录 视图 触发器 存储过程 函数 事务 数据库锁 数据库备份 事件 一.视图 1.视图概念 视图是一个虚拟表, ...
- MySQL之视图学习
MYSQL---视图 1.概述: 视图是从一个或者多个表中导出的,视图的行为与表非常类似,但视图是一个虚拟表.在视图中用户可以使用SELECT语句查询数据,以及使用INSERT.UPDATE和DE ...
- mysql第五篇 : MySQL 之 视图、触发器、存储过程、函数、事物与数据库锁
第五篇 : MySQL 之 视图.触发器.存储过程.函数.事物与数据库锁 一.视图 视图是一个虚拟表(非真实存在的),其本质是‘根据SQL语句获取动态的数据集,并为其命名‘ ,用户使用时只需使用“名称 ...
- mysql数据库视图连接出现2003····错误
MySQL利用视图工具连接数据库时出现2003····错误 原因:MySQL的服务没有开启 解决步骤: ...
- mysql 查询表,视图,触发器,函数,存储过程
1. mysql查询所有表: SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '数据库名' AND TAB ...
- mysql view(视图)
一,什么是视图 视图是存放数据的一个接口,也可以说是虚拟的表.这些数据可以是从一个或几个基本表(或视图)的数据.也可以是用户自已定义的数据.其实视图里面不存放数据的,数据还是放在基本表里面,基本表里面 ...
随机推荐
- flutter 使用阿里iconfont图标库
1. 打开Iconnfont,选择自己想要的图标添加到购物车! 2,在右上角点开购物车选择下载代码. 3. 解压下载的代码压缩包,我们可以看到一个iconfont.ttf 4. 在项目根目录下创建一个 ...
- SpringMVC-nfjh
SpringMVC springmvc项目创建 1.使用maven创建web项目结构 2.补充更改结构 3.springmvc-config.xml 1)添加包扫描(context命名空间) 2)添加 ...
- 1.2 Git&Github
Git&GitHub 一.必做部分 1.Git的安装与命令学习 下载&安装 PC端科学经费不足所以Github下载一直失败,最后去官网https://gitforwindows.org ...
- C++入门之unordered_map
1.介绍 unordered_map是c++语言STL库中一个比较重要的容器,不能自动排序,这一容器是根据哈希表这一数据结构设计而成的,能够极大地提升数据搜索.插入和删除操作的时间效率. 2.头文 ...
- bzoj 2337
有人说这题像游走... 关于游走的思想,他死了... 明明直接从期望dp的角度考虑更简单合理嘛 首先由于是异或运算不妨逐位考虑 对于每一位,设状态$f[i]$表示从第$i$个点到第$n$个点,这一位上 ...
- 不可错过的JS代码优化技巧(持续更新)
1. 带有多个条件的 if 语句 把多个值放在一个数组中,然后调用数组的 includes 方法. //longhand if (x === 'abc' || x === 'def' || x === ...
- #Python #字符画 #灰度图 使用Python绘制字符画及其原理
由于最近身体状况不太好所以更新会有点慢,请大家多多包涵.同时也提醒大家注意保重身体! 前提:默认大家已经正确安装了 Python,且正确将Python配置到了系统Path . 目录 1.字符画的概况 ...
- 【python】第一模块 步骤四 第一课、初始正则表达式
第一课.初始正则表达式 一.课程介绍 1.1 课程概要 步骤介绍 正则表达式入门及应用 正则的进阶 案例 综合项目实战 二.正则表达式的基本操作(多敲代码多做练习) 2.1 什么是正则表达式 什么是正 ...
- 20220719 第七组 陈美娜 Java(this,封装,构造器概念)
1.关于构造器 如果说创建对象仅仅是为了调用这个类的方法,建议使用无参构造器 如果说创建对象的时候需要使用到对象的某个属性,可以使用构造器赋值 2.this关键字 this代表的是当前类的对象,thi ...
- node后台项目所需中间件梳理
0.nodemon 全局工具,监听项目文件变动,并自动重启项目 一.node内置模块 1.fs fs.readFile() 读取指定文件中的内容fs.writeFile() 向指定的文件中写入内容 ...