MySQL使用可重复读作为默认隔离级别的原因
一般的DBMS系统,默认都会使用读提交(Read-Comitted,RC)作为默认隔离级别,如Oracle、SQL Server等,而MySQL却使用可重复读(Read-Repeatable,RR)。要知道,越高的隔离级别,能解决的数据一致性问题越多,理论上性能损耗更大,可并发性越低。隔离级别依次为
SERIALIZABLE > RR > RC > Read-Uncommited
在SQL标准中,前三种隔离级别分别解决了幻象读、不可重复读和脏读的问题。那么,为什么MySQL使用可重复读作为默认隔离级别呢?
1. 从Binlog说起
Binlog是MySQL的逻辑操作日志,广泛应用于复制和恢复。MySQL 5.1以前,Statement是Binlog的默认格式,即依次记录系统接受的SQL请求;5.1及以后,MySQL提供了Row和Mixed两个Binlog格式。
从MySQL 5.1开始,如果打开语句级Binlog,就不支持RC和Read-Uncommited隔离级别。要想使用RC隔离级别,必须使用Mixed或Row格式。
mysql> set tx_isolation='read-committed'; Query OK, 0 rows affected (0.00 sec) mysql> insert into t1 values(1,1); ERROR 1598 (HY000): Binary logging not possible. Message: Transaction level 'READ-COMMITTED' in InnoDB is not safe for binlog mode 'STATEMENT' |
那么,为什么RC隔离级别不支持语句级Binlog呢?我们关闭binlog,做以下测试。
会话1 |
会话2 |
use test; #初始化数据 create table t1(c1 int, c2 int) engine=innodb; create table t2(c1 int, c2 int) engine=innodb; insert into t1 values(1,1), (2,2); insert into t2 values(1,1), (2,2); #设置隔离级别 set tx_isolation='read-committed'; Query OK, 0 rows affected (0.00 sec) #连续更新两次 mysql> Begin; Query OK, 0 rows affected (0.03 sec) mysql> update t2 set c2 = 3 where c1 in (select c1 from t1); Query OK, 2 rows affected (0.00 sec) Rows matched: 2 Changed: 2 Warnings: 0 mysql> update t2 set c2 = 4 where c1 in (select c1 from t1); Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> select * from t2; +------+------+ | c1 | c2 | +------+------+ | 1 | 4 | | 2 | 3 | +------+------+ 2 rows in set (0.00 sec) mysql> commit; |
#设置隔离级别 set tx_isolation='read-committed'; Query OK, 0 rows affected (0.00 sec) #两次更新之间执行删除 mysql> delete from t1 where c1 = 2; Query OK, 1 row affected (0.03 sec) |
由以上测试知,RC隔离级别下,会话2执行时序在会话1事务的语句之间,并且会话2的操作影响了会话1的结果,这会对Binlog结果造成影响。
由于Binlog中语句的顺序以commit为序,如果语句级Binlog允许,两会话的执行时序是
#会话2 set tx_isolation='read-committed'; delete from t1 where c1 = 2; commit; #会话1 set tx_isolation='read-committed'; Begin; update t2 set c2 = 3 where c1 in (select c1 from t1); update t2 set c2 = 4 where c1 in (select c1 from t1); select * from t2; +------+------+ | c1 | c2 | +------+------+ | 1 | 4 | | 2 | 2 | +------+------+ 2 rows in set (0.00 sec) commit; |
由上可知,在MySQL 5.1及以上的RC隔离级别下,语句级Binlog在DR上执行的结果是不正确的!
那么,MySQL 5.0呢?5.0允许RC下语句级Binlog,是不是说很容易产生DB/DR不一致呢?
事实上,在5.0重复上述一个测试,并不存在这个问题,原因是5.0的RC与5.1的RR使用类似的并发和上锁机制,也就是说,MySQL 5.0的RC与5.1及以上的RC可能存在兼容性问题。
下面看看RR是怎么解决这个问题的。
2. 默认隔离级别-可重复读
导致RC隔离级别DB/DR不一致的原因是:RC不可重复读,而Binlog要求SQL串行化!
在RR下,重复以上测试
会话1 |
会话2 |
use test; #初始化数据 create table t1(c1 int, c2 int) engine=innodb; create table t2(c1 int, c2 int) engine=innodb; insert into t1 values(1,1), (2,2); insert into t2 values(1,1), (2,2); #设置隔离级别 set tx_isolation='repeatable-read'; Query OK, 0 rows affected (0.00 sec) #连续更新两次 mysql> Begin; Query OK, 0 rows affected (0.03 sec) mysql> update t2 set c2 = 3 where c1 in (select c1 from t1); Query OK, 2 rows affected (0.00 sec) Rows matched: 2 Changed: 2 Warnings: 0 mysql> update t2 set c2 = 4 where c1 in (select c1 from t1); Query OK, 2 rows affected (0.00 sec) Rows matched: 2 Changed: 2 Warnings: 0 mysql> select * from t2; +------+------+ | c1 | c2 | +------+------+ | 1 | 4 | | 2 | 4 | +------+------+ 2 rows in set (0.00 sec) mysql> commit; |
#设置隔离级别 set tx_isolation=' repeatable-read'; Query OK, 0 rows affected (0.00 sec) #两次更新之间执行删除 mysql> delete from t1 where c1 = 2; --阻塞,直到会话1提交 Query OK, 1 row affected (18.94 sec) |
与RC隔离级别不同的是,在RR中,由于保证可重复读,会话2的delete语句会被会话1阻塞,直到会话1提交。
在RR中,会话1语句update t2 set c2 = 3 where c1 in (select c1 from t1)会先在t1的记录上S锁(5.1的RC中不会上这个锁,但5.0的RC会),接着在t2的满足条件的记录上X锁。由于会话1没提交,会话2的delete语句需要等待会话1的S锁释放,于是阻塞。
因此,在RR中,以上测试会话1、会话2的依次执行,与Binlog的顺序一致,从而保证DB/DR一致。
幻象读
除了保证可重复读,MySQL的RR还一定程度上避免了幻象读(幻象读是由于插入导致的新记录)。(为什么说一定程度呢?参考第3节可重复读和串行化的区别。)
会话1 |
会话2 |
use test; #初始化数据 create table t1(c1 int primary key, c2 int) engine=innodb; create table t2(c1 int primary key, c2 int) engine=innodb; insert into t1 values(1,1), (10,10); insert into t2 values(1,1), (5,5), (10,10); #设置隔离级别 set tx_isolation='repeatable-read'; Query OK, 0 rows affected (0.00 sec) #连续更新两次 mysql> Begin; Query OK, 0 rows affected (0.03 sec) mysql> update t2 set c2 = 20 where c1 in (select c1 from t1); Query OK, 2 rows affected (0.00 sec) Rows matched: 2 Changed: 2 Warnings: 0 mysql> delete from where c1 in (select c1 from t1); Query OK, 2 rows affected (0.00 sec) Rows matched: 2 Changed: 2 Warnings: 0 mysql> select * from t2; +------+------+ | c1 | c2 | +------+------+ | 5 | 5 | +------+------+ 2 rows in set (0.00 sec) mysql> commit; |
#设置隔离级别 set tx_isolation=' repeatable-read'; Query OK, 0 rows affected (0.00 sec) #两次更新之间执行插入 mysql> insert into t1 values(5,5); --阻塞,直到会话1提交 Query OK, 1 row affected (18.94 sec) |
由上述例子知,会话2的插入操作被阻塞了,原因是RR隔离级别中,除了记录锁外,还会上间隙锁(gap锁)。例如,对于表t1,update t2 set c2 = 20 where c1 in (select c1 from t1)以上的锁包括:
(-∞, 1), 1, (1, 10), 10, (10, +∞)
由于对t1做全表扫描,因此,所有记录和间隙都要上锁,其中(x,y)表示间隙锁,数字表示记录锁,全部都是S锁。会话2的insert操作插入5,位于间隙(1,10),需要获得这个间隙的X锁,因此两操作互斥,会话2阻塞。
SQL标准的RR并不要求避免幻象读,而InnoDB通过gap锁来避免幻象,从而实现SQL的可串行化,保证Binlog的一致性。
要想取消gap lock,可使用参数innodb_lock_unsafe_for_binlog=1,默认为0。
3. 可重复读与串行化的区别
InnoDB的RR可以避免不可重复读和幻象读,那么与串行化有什么区别呢?
会话1 |
会话2 |
use test; #初始化数据 create table t3(c1 int primary key, c2 int) engine=innodb; #设置隔离级别 set tx_isolation='repeatable-read'; Query OK, 0 rows affected (0.00 sec) mysql> Begin; Query OK, 0 rows affected (0.03 sec) mysql> select * from t3 where c1 = 1; Empty set (0.00 sec) mysql> select * from t3 where c1 = 1; Empty set (0.00 sec) mysql> update t3 set c2 =2 where c1 = 1; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> select * from t3 where c1 = 1; +----+------+ | c1 | c2 | +----+------+ | 1 | 2 | +----+------+ 1 row in set (0.00 sec) mysql> commit; |
#设置隔离级别 set tx_isolation=' repeatable-read'; Query OK, 0 rows affected (0.00 sec) mysql> insert into t3 values(1,1); Query OK, 1 row affected (0.05 sec) |
由上述会话1中,连续两次读不到数据,但更新却成功,并且更新后的相同读操作就能读到数据了,这算不算幻读呢?
其实,RR隔离级别的防止幻象主要是针对写操作的,即只保证写操作的可串行化,因为只有写操作影响Binlog;而读操作是通过MVCC来保证一致性读(无幻象)。
然而,可串行化隔离级别要求读写可串行化。使用可串行化重做以上测试。
会话1 |
会话2 |
use test; #初始化数据 create table t3(c1 int primary key, c2 int) engine=innodb; #设置隔离级别 set tx_isolation='SERIALIZABLE'; Query OK, 0 rows affected (0.00 sec) mysql> Begin; Query OK, 0 rows affected (0.03 sec) mysql> select * from t3 where c1 = 1; Empty set (0.00 sec) mysql> select * from t3 where c1 = 1; Empty set (0.00 sec) mysql> update t3 set c2 =2 where c1 = 1; Query OK, 0 rows affected (0.00 sec) Rows matched: 0 Changed: 0 Warnings: 0 mysql> select * from t3 where c1 = 1; Empty set (0.00 sec) mysql> commit; |
#设置隔离级别 set tx_isolation='SERIALIZABLE'; Query OK, 0 rows affected (0.00 sec) mysql> insert into t3 values(1,1); #阻塞,直到会话1提交 Query OK, 1 row affected (48.90 sec) |
设置为串行化后,会话2的插入操作被阻塞。由于在串行化下,查询操作不在使用MVCC来保证一致读,而是使用S锁来阻塞其他写操作。因此做到读写可串行化,然而换来就是并发性能的大大降低。
4. 小结
MySQL使用可重复读来作为默认隔离级别的主要原因是语句级的Binlog。RR能提供SQL语句的写可串行化,保证了绝大部分情况(不安全语句除外)的DB/DR一致。
另外,通过这个测试发现MySQL 5.0与5.1在RC下表现是不一样的,可能存在兼容性问题。
原文:https://blog.csdn.net/shudaqi2010/article/details/79958222
MySQL使用可重复读作为默认隔离级别的原因的更多相关文章
- MySQL选用可重复读之前一定要想到的事情
原文地址:http://blog.itpub.net/29254281/viewspace-1398273/ MySQL选用可重复读隔离级别之前一定要想到的事情.间隙锁 MySQL在使用之前有三个务必 ...
- MySQL的可重复读级别能解决幻读吗
引言 之前在深入了解数据库理论的时候,了解到事物的不同隔离级别可能存在的问题.为了更好的理解所以在MySQL数据库中测试复现这些问题.关于脏读和不可重复读在相应的隔离级别下都很容易的复现了.但是对于幻 ...
- 【MySQL】可重复读模式下 unique key失效案例
一 [背景] 今天上午文能提笔安天下,武能上马定乾坤的登博给团队出了一道题目,谁先复现问题,奖励星巴克一杯.激起了一群忙碌的屌丝DBA的极大热情.问题是这样滴,如下图登博提示了几个细节: 1. ...
- MySQL的可重复读级别能解决幻读问题吗?
之前在深入了解数据库理论的时候,了解到事务的不同隔离级别可能存在的问题.为了更好的理解所以在MySQL数据库中测试复现这些问题.关于脏读和不可重复读在相应的隔离级别下都很容易的复现了. 但是对于幻读, ...
- 面试官:MySQL的可重复读级别能解决幻读问题吗?
引言 之前在深入了解数据库理论的时候,了解到事务的不同隔离级别可能存在的问题.为了更好的理解所以在MySQL数据库中测试复现这些问题.关于脏读和不可重复读在相应的隔离级别下都很容易的复现了. 但是对于 ...
- mysql系列:加深对脏读、脏写、可重复读、幻读的理解
关于相关术语的专业解释,请自行百度了解,本文皆本人自己结合参考书和自己的理解所做的阐述,如有不严谨之处,还请多多指教. 事务有四种基本特性,叫ACID,它们分别是: Atomicity-原子性,Con ...
- MySQL 日志之 binlog 格式 → 关于 MySQL 默认隔离级别的探讨
开心一刻 产品还没测试直接投入生产时,这尼玛... 背景问题 在讲 binlog 之前,我们先来回顾下主流关系型数据库的默认隔离级别,是默认隔离级别,不是事务有哪几种隔离级别,别会错题意了 1.Ora ...
- Mysql可重复读原理
mysql可重复读现象及原理分析 InnoDB---可重复读隔离级别的底层实现原理 概念 可重复读的实现 Repeatable Read(可重复读):一个事务在执行过程中可以看到其他事务已经提交的新插 ...
- MySQL Transaction--MySQL与SQL Server在可重复读事务隔离级别上的差异
MySQL和SQL Server两种数据库在REPEATABLE-READ事务隔离级别实现方式不同,导致使用上也存在差异. 在MySQL中,默认使用REPEATABLE-READ事务隔离级别,MySQ ...
随机推荐
- ArcGIS API For Javascript:新增热力图层的方法
当我们要制作一个热力图层,可以通过以下 3 步来实现: 引入类 在 require 中需引入 "esri/layers/FeatureLayer", "esri/rend ...
- 阅读《Windows 黑客编程技术详解》(甘迪文著)【正在进行】
内容提要: 本书介绍的是黑客编程的基础技术,涉及用户层下的Windows编程和内核层下的Rootkit编程. 全书大纲: 第一篇 用户篇 平常计算机上使用的应用程序,都运行在用户层上,属于用户程序.在 ...
- 题解-洛谷P2010-回文日期
原题链接: https://www.luogu.org/problem/P2010 题目简述: 牛牛习惯用8位数字表示一个日期,其中,前4位代表年份,接下来2位代表月份,最后22位代表日期.显然:一个 ...
- Spring-boot(一)通过向导快速创建Spring-boot项目
通过向导快速创建Spring-boot项目 创建步骤: 选择Spring Initializr 填写组织和模块名 选择对应的模块 注:这里左侧的模块比较多,玩家可以根据自己的实际需要自由选择,此处暂时 ...
- mr的partition分区
1.Partitioner 组件通过让 Map 对 Key 进行分区,从而将不同分区的 Key 交由不同的 Reduce 处理.Partition属于map端 2.分区的总数与任务的reduce任务数 ...
- Linux目录结构-上部
第1章 目录结构 1.1 目录结构特点 倒挂的树状结构一切从根开始一切皆文件 1.2 目录结构 /bin 二进制文件 命令 /sbin 超级命令只有root ...
- 把Git Repository建到U盘上去
Git很火.原因有三: 它是大神Linus Torvalds的作品,天然地具备神二代的气质和品质: 促进了生产力的发展,Git的分布式版本控制理念,并非首创,但非常适合开源社区的协作方式(不存在mas ...
- 使用Jq实现弹出层
对于前端程序员来说,弹出层是经常用到的,下面我叫大家如何用实现JQuery实现弹出层的效果,代码如下: CSS:弹出层的效果 .mask { position: absolute; top: 0px; ...
- 关于c++函数里面return的用法,关于调用的讲解
与下面的图片对比一下 可以看见在int b = test();d的时候cout<<"hello";就被调用了: cout<<b;只是返回return a的值 ...
- http状态码_____ 204/206/200
HTTP的状态码有很多种,主要有五个大类 1xx(临时响应) 2xx(成功) 3xx(已重定向) 4xx(请求错误) 5xx(服务器错误) 每个大类还对应一些具体的分类.平时我们接触比较多的是200. ...