更多内容,前往IT-BLOG

MySQL中支持的四种隔离级别


提到事务,你肯定会想到 ACIDAtomicityConsistencyIsolationDurability,即原子性一致性隔离性持久性),来说说其中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 COMMITTEDREPEATABLE 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 COMMITTEDREPEATABLE 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)的更多相关文章

  1. MySQL——一致性非锁定读(快照读)&MVCC

    MySQL--一致性非锁定读(快照读) MySQL数据库中读分为一致性非锁定读.一致性锁定读 一致性非锁定读(快照读),普通的SELECT,通过多版本并发控制(MVCC)实现. 一致性锁定读(当前读) ...

  2. 温故知新-Mysql锁&事务&MVCC

    文章目录 锁概述 锁分类 MyISAM 表锁 InnoDB 行锁 事务及其ACID属性 InnoDB 的行锁模式 注意 MVCC InnoDB 中的 MVCC 参考 你的鼓励也是我创作的动力 Post ...

  3. MySQL锁与MVCC

    --MySQL锁与MVCC --------------------2014/06/29 myisam表锁比较简单,这里主要讨论一下innodb的锁相关问题. innodb相比oracle锁机制简单许 ...

  4. 第五章 MySQL事务,视图,索引,备份和恢复

    第五章 MySQL事务,视图,索引,备份和恢复 一.事务 1.什么是事务 事务是一种机制,一个操作序列,它包含了一组数据库操作命令,并且把所有的命令作为一个整体一起向系统提交或撤销操作请求.要么都执行 ...

  5. 数据库MySQL之 视图、触发器、存储过程、函数、事务、数据库锁、数据库备份、事件

    数据库MySQL之 视图.触发器.存储过程.函数.事务.数据库锁.数据库备份.事件 浏览目录 视图 触发器 存储过程 函数 事务 数据库锁 数据库备份 事件 一.视图 1.视图概念 视图是一个虚拟表, ...

  6. MySQL之视图学习

    MYSQL---视图 1.概述: ​ 视图是从一个或者多个表中导出的,视图的行为与表非常类似,但视图是一个虚拟表.在视图中用户可以使用SELECT语句查询数据,以及使用INSERT.UPDATE和DE ...

  7. mysql第五篇 : MySQL 之 视图、触发器、存储过程、函数、事物与数据库锁

    第五篇 : MySQL 之 视图.触发器.存储过程.函数.事物与数据库锁 一.视图 视图是一个虚拟表(非真实存在的),其本质是‘根据SQL语句获取动态的数据集,并为其命名‘ ,用户使用时只需使用“名称 ...

  8. mysql数据库视图连接出现2003····错误

    MySQL利用视图工具连接数据库时出现2003····错误                                                  原因:MySQL的服务没有开启 解决步骤: ...

  9. mysql 查询表,视图,触发器,函数,存储过程

    1. mysql查询所有表: SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '数据库名' AND  TAB ...

  10. mysql view(视图)

    一,什么是视图 视图是存放数据的一个接口,也可以说是虚拟的表.这些数据可以是从一个或几个基本表(或视图)的数据.也可以是用户自已定义的数据.其实视图里面不存放数据的,数据还是放在基本表里面,基本表里面 ...

随机推荐

  1. jmeter中response data出现乱码的解决方法

    步骤如下:1.进入jmeter\apache-jmeter-5.1.1\bin,打开jmeter.properties2.jmeter.properties中搜索"sampleresult. ...

  2. if __name__ == '__main__':中的语句无法执行

    在pycarm中我们用了pytest或unittest框架写测试用例,我们如果我们在最后加上if name == 'main':,如以下代码所示.最后我们右键点击运行的时候是不会执行**if name ...

  3. jdkman(jdk版本管理工具)安装和使用(mac)

    1.安装jdkman 1.1.下载命令 curl -s "https://get.sdkman.io" | bash 执行后,sdkman安装到目录$HOME/.sdkman/,比 ...

  4. excel的几个常用方法

    --笔记开始: 1.if(条件,真值,假值),类似于编程语言中的三元运算符.条件为真时返回真值,条件为假时返回假值. 2.match(目标值,查找区域,查找类型),一般查找类型为0(等值查找),查找区 ...

  5. Calendar设定月份时要注意日期

    先看下代码 public static void main(String[] args) { int dataMonth = 4; DateFormat dateFormat = new Simple ...

  6. linux-vi/vim常用操作键

    命令模式: :w 将编辑的数据写入硬盘档案中(常用) :w! 若文件属性为『只读』时,强制写入该档案.不过,到底能不能写入, 还是跟你对该档案的档案权限有关啊! :q 离开 vi (常用) :q! 若 ...

  7. 学习JavaScript第五周

    MySQL基本内容: 访问:2种 ​ 1.图形化界面 - 傻瓜式 ​ 要求:同时打开apache和mysql ​ 访问:127.0.0.1:端口号/phpmyadmin ​ localhost:端口号 ...

  8. 使用TLS/SSL传输层安全机制实现web项目的通信安全

    自己的web项目在内网ip访问时,浏览器会提示不安全 原因就是因为没有证书,而传输层的TLS/SSL协议,会告诉我们本地客户端的浏览器,我现在访问的web项目的ip地址可能存在安全风险 要解决这个通信 ...

  9. 十大经典排序之计数排序(C++实现)

    计数排序 核心思想:计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中. 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数. 思路: 找出待排序的数组中最大和 ...

  10. 对表单input输入框加特殊符号(正斜杠和反斜杠)校验

    <p>图片名称:</p><input type="text" name="afterName" style="heigh ...