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(视图)
一,什么是视图 视图是存放数据的一个接口,也可以说是虚拟的表.这些数据可以是从一个或几个基本表(或视图)的数据.也可以是用户自已定义的数据.其实视图里面不存放数据的,数据还是放在基本表里面,基本表里面 ...
随机推荐
- List一边插入数据后又移除数据
记录最简单的三种方法,直接上代码: List<String> list = new ArrayList<>(); list.add("1"); list.a ...
- linux-vi/vim常用操作键
命令模式: :w 将编辑的数据写入硬盘档案中(常用) :w! 若文件属性为『只读』时,强制写入该档案.不过,到底能不能写入, 还是跟你对该档案的档案权限有关啊! :q 离开 vi (常用) :q! 若 ...
- 4组-Alpha冲刺-总结
组长博客链接 一.基本情况 1.1现场答辩总结 1.1.1柯老师的建议与问题: 界面不够美观,要求达到看不出来是学生作品的水平. 答:好的,我们会进一步改进. alpha完成程度? 答:完成到60%以 ...
- module ‘pip‘ has no attribute ‘pep425tags‘的解决方案
可行方案: E:\pyth\Anaconda\envs>python -m pip debug --verboseWARNING: This command is only meant for ...
- pytorch 简简单单求个值
能用张量处理就用张量,不要使用for in 跑循环,一个是容易出错,一个是比较浪费时间,应用广播机制的话去做很容易的 1.5 使用mean处理平均值 2. 在处理梯度的时候无法更改自身,因此使用的办法 ...
- jmeter的三种参数化方式
一.通过添加前置处理器(用户参数) 1. 在http层级下添加--前置处理器--用户参数 2.可以修改名称,每次迭代更新一次(一定要勾选上),这样才会每次迭代变量值也更新 ,点击下面添加用户(多次测试 ...
- 关于PLC的脉冲输出(S7-300)
1. 关于脉冲输出 脉冲输出的方法有很多: 如果要产生占空比为50%的脉冲信号: ① 用S7-300PLC的时钟存储器 右键点击PLC,选中时钟存储器,默认存储字节为0. 各时钟存储器的周 ...
- php上传文件时出现 caution: request is not finished yet
其中的一个原因:是wamp64下的tmp文件夹中的临时文件太多,把这个文件夹的临时文件清理后就可以了.
- VBA类模块完全教程(www.accessoft.com软件网)
这份礼物送给现在想学习类知识或曾经学过但因为各种原因没有"修成正果"的朋友,我期望的结果是这篇文章后,您可以在类模块中像在标准模块中写代码一样熟练,我也期望不至于太乏味而使您没有耐 ...
- java实现前n项和,要求不使用循环、乘除法、判断标识
public class Ceui3 { public static int sum = 0; public static void main(String[] args) { System.out. ...