参考:http://www.cnblogs.com/vinchen/archive/2012/11/19/2777919.html

一般的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下表现是不一样的,可能存在兼容性问题。

参考

http://dev.mysql.com/doc/refman/5.1/en/binary-log-mixed.html

http://dev.mysql.com/doc/refman/5.1/en/set-transaction.html

http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html

http://dev.mysql.com/doc/refman/5.5/en/innodb-parameters.html#sysvar_innodb_locks_unsafe_for_binlog

http://blog.bitfly.cn/post/mysql-innodb-phantom-read/

MySQL使用可重复读作为默认隔离级别的原因相关推荐

  1. mysql解决不可重复读_mysql怎么解决不可重复读

    mysql解决不可重复读的方法:采用了mvcc多版本并发控制,mvcc是利用在每条数据后面加了隐藏的两列,即创建版本号和删除版本号,每个事务在开始的时候都会有一个递增的版本号. [相关学习推荐:mys ...

  2. mysql防止不可重复读_mysql怎么解决不可重复读

    mysql解决不可重复读的方法:采用了mvcc多版本并发控制,mvcc是利用在每条数据后面加了隐藏的两列,即创建版本号和删除版本号,每个事务在开始的时候都会有一个递增的版本号. [相关学习推荐: my ...

  3. Mysql在可重复读事务隔离级别下怎么解决幻读的

    目录 前言 并发事务产生的问题 更新丢失 回滚丢失 覆盖丢失 脏读 不可重复读 幻读 快照读和当前读 幻读验证 快照读如何避免幻读 当前读如何避免幻读 可重复读隔离级别发生幻读情况 小结 前言 Mys ...

  4. 【MySQL】可重复读模式下 unique key失效案例

    一 [背景]    今天上午文能提笔安天下,武能上马定乾坤的登博给团队出了一道题目,谁先复现问题,奖励星巴克一杯.激起了一群忙碌的屌丝DBA的极大热情.问题是这样滴,如下图 登博提示了几个细节:   ...

  5. mysql幻读和不可重复读的区别_面试官:MySQL的可重复读级别能解决幻读吗

    Java面试笔试面经.Java技术每天学习一点 Java面试 关注不迷路 作者:宁愿. 来源:https://juejin.im/post/5c9040e95188252d92095a9e 引言 之前 ...

  6. 不可重复读和幻读的区别_面试官:MySQL的可重复读级别能解决幻读吗

    Java面试笔试面经.Java技术每天学习一点 Java面试 关注不迷路 作者:宁愿. 来源:https://juejin.im/post/5c9040e95188252d92095a9e 引言 之前 ...

  7. mysql串行化防幻读原理_透彻解读mysql的可重复读、幻读及实现原理

    目录 一.事务的隔离级别 二.mysql怎么实现的可重复读 举例说明MVCC的实现 MVCC逻辑流程-插入 MVCC逻辑流程-删除 MVCC逻辑流程-修改 MVCC逻辑流程-查询 三.幻读 快照读和当 ...

  8. MYSQL 如何实现重复读

    1.可重复读 可重复读(REPEATABLE READ)是MySQL的默认隔离级别,具体是指在同一事务中多次读取的数据是一致的. 2.如何实现 MySQL是使用多版本并发控制(MVCC,Mutil-V ...

  9. mysql实现可重复读(解决幻读)的原理(MVCC机制的版本链和读视图)

    ​​​​​​​ 版本链 对于使用InnoDB存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列(row_id并不是必要的,我们创建的表中有主键或者非NULL唯一键时都不会包含row_id列): ...

最新文章

  1. 深度学习基础(基本概念、优化算法、初始化、正则化等)
  2. Java实现算法导论中KMP字符串匹配算法
  3. 计算机教学实验操作环境,一种基于虚拟机的个性化计算机实验教学环境.pdf
  4. SpringBoot 逻辑判断
  5. openjdk platform binary是什么进程_基于pytest实现appium多进程兼容性测试
  6. 在制造业中推进机器人技术的五种方法
  7. 这几年人工智能和大数据的真实就业规律
  8. 如何在.NET Core控制台程序中使用依赖注入
  9. [CQOI]九连环(FFT优化+高精)
  10. eos和以太坊有什么关系_【EOS价格分析】EOS,宇宙,以太坊价格分析:8月6日
  11. 输入字符_你会输入带圈字符吗?
  12. 华为二层创建vlan_华为二层交换机基本配置命令有哪些
  13. java建设银行支付_基于JAVA的建设银行账目管理系统的设计
  14. 01-unity下载与安装
  15. IP信息解析和地理定位,以及免费GeoLite2-City.mmdb的使用教程
  16. html5 网易公开课,麻省理工学院公开课:单变量微积分习题课
  17. 云计算 三种模式 各种云的区别
  18. qq邮箱imtp收件服务器,qq邮箱代收outlook
  19. iOS开发中解决第三方静态库符号冲突的终极方案
  20. vue项目中的小知识--快捷键-vue插件版本号--vscode插件等

热门文章

  1. 5G NGC — 关键技术 — 网络切片 — 切片的选择
  2. C 语言编程 — GCC 工具链
  3. minicom的使用,发送AT指令
  4. Keil uVision5 下载程序 add flash programming algorithm选项缺少需要的下载算法的解决办法
  5. 2019-4-23 plan
  6. 集成 dubbo 微服务
  7. Java 8 Lambda 表达式解析
  8. iOS 利用RunTime检测控制器是否销毁
  9. fail2ban防止暴力破解
  10. Javascript入门视频教程