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(视图)
一,什么是视图 视图是存放数据的一个接口,也可以说是虚拟的表.这些数据可以是从一个或几个基本表(或视图)的数据.也可以是用户自已定义的数据.其实视图里面不存放数据的,数据还是放在基本表里面,基本表里面 ...
随机推荐
- Java基础学习:2、Java数据类型及逻辑运算符
1.数据类型: 数据类型范围: byte:-2^7 ~ 2^7-1,即-128 ~ 127.1字节.Byte.末尾加B short:-2^15 ~ 2^15-1,即-32768 ~ 32767.2字节 ...
- Myeclipse导入项目提示已存在(Some projects cannot be imported because they already exist in the workspace)
原因:在项目空间的文件夹中没有删除干净之前的项目,之前的项目和要导入的项目的名字相同,所以导致无法将项目导入.需要在项目空间中将有关的旧文件都清理干净. 1.将项目空间文件夹中的项目删除. 2.到:\ ...
- nkIO方法
import java.util.*; public class Main{ public static void main(String args[]){ Scanner sc = new Scan ...
- JDK1.8下载、安装和环境配置教程——
JDK1.8下载.安装和环境配置教程 1.下载安装包 - 浏览器搜索JDK8,如下图: - 点击网页打开后,下拉找到这个: - 根据自己的系统选择正确的进行下载: 例如我的是windows 64位,我 ...
- linux查看进程信息
top 实时查看进程信息,展示进程id,使用内存,占用cpu等信息,可以查看内容占用最多.cpu使用最多的进程,然后再根据进程id查看进程的详细信息.实时更新 ps 瞬时查看进程情况,ps -ef | ...
- Jetpack Compose 加载 Drawable
Drawable Painter A library which provides a way to use Android drawables as Jetpack Compose Painters ...
- docker build提示 error checking context:can't stat xxx
这个提示基本是权限不够外加目录结构不对,保险的做法直接删除 Dockerfile. 正确做法:(可能还有其他的解决方案吧,这个可以生效) 重新建个目录,给目录授权,然后再dockerfile文件夹中建 ...
- 20220719 第七组 陈美娜 Java(this,封装,构造器概念)
1.关于构造器 如果说创建对象仅仅是为了调用这个类的方法,建议使用无参构造器 如果说创建对象的时候需要使用到对象的某个属性,可以使用构造器赋值 2.this关键字 this代表的是当前类的对象,thi ...
- function | fastica
fastica - Fast Independent Component Analysis FastICA for Matlab 7.x and 6.x Version 2.5, October 19 ...
- python 购物小程序
要求: 1.启动程序后,让用户输入预算,然后打印商品列表 2.允许用户根据商品编号购买商品 3.用户选择商品后,检测余额够不够,够就直接付款,不够就提醒 4.可随时退出,推出时打印已购买商品和余额 ...