【转载】MySQL使用可重复读作为默认隔离级别的原因

最近由于工作的一部分原因,在看mysql隔离级别的相关内容。发现之前自己的认知有一点偏差:
之前我一直认为mysql的RR隔离级别没办法解决幻读的问题。
这个理解是有问题的,mysql RR通过间隙锁(gap锁)是能够解决一部分的幻读的问题(由于写引起的);但是对于读导致的幻读,mysql是并不解决的。mysql是通过MVCC来解决一致性读的问题的。

看到了一篇文章对于这个问题有比较详细的介绍,转过来与君共勉。

文章转载自vinchen的博客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下表现是不一样的,可能存在兼容性问题。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • 一、事务 1、事务四要素:ACID 对于事务,我之前的理解是很粗糙的,不就是为了保证操作的原子性么?一般订单系统或...
    张伟科阅读 1,262评论 0 5
  • Innodb中的事务隔离级别和锁的关系 转发(https://tech.meituan.com/innodb_lo...
    淼淼先森阅读 399评论 0 0
  • 前言:我们都知道事务的几种性质,数据库为了维护这些性质,尤其是一致性和隔离性,一般使用加锁这种方式。同时数据库又是...
    bbe9e62bc5ba阅读 736评论 0 2
  • 我们都知道事务的几种性质,数据库为了维护这些性质,尤其是一致性和隔离性,一般使用加锁这种方式。同时数据库又是个高并...
    流浪冰007阅读 1,077评论 0 2
  • 我本人是一个非常矛盾冲突很多很多纠结的能量!我上午的想法可能下午因为一点小事一句话就被打乱了!我身边的人都知道...
    lingjiyidong阅读 158评论 0 0