天天看点

为什么MySQL默认的隔离级别是RR而大厂使用的是RC?

1写作目的

现在的服务都是分布式,MySQL的集群架构也是一样。那么MySQL的集群架构中有一个点是读写分离,而读写分离是基于binlog实现的。那么接下来就MySQL的读写分离和binlog为突破点进行分析为什么大厂中的默认隔离级别是RC。总体来说以时间线为基准进行讲解。

2binlog格式

为什么MySQL默认的隔离级别是RR而大厂使用的是RC?

3为什么MySQL默认的隔离级别是RR

参考:​​互联网项目中mysql应该选什么事务隔离级别​​ 在Oracle,SqlServer中都是选择读已提交(Read Commited)作为默认的隔离级别,为什么Mysql不选择读已提交(Read Commited)作为默认隔离级别,而选择可重复读(Repeatable Read)作为默认的隔离级别呢?

首先要明确一点:MySQL需要主从复制!。主从复制需要binlog和事务的隔离级别。

历史原因:5.0版本及之前binlog只支持STATEMENT这种格式。为了支持主从复制,那么事务的隔离级别只能是可重复读(Repeatable Read)。

因为当binlog为STATEMENT格式,且隔离级别为读已提交(Read Commited)时,会有bug导致主从库的数据不一致。

如下图所示,在主(master)上执行如下事务。

为什么MySQL默认的隔离级别是RR而大厂使用的是RC?

此时在主(master)上执行下列语句

select * from test;      

输出如下

+---+
| b |
+---+
| 3 |
+---+
1 row in set      

但是,你在此时在从(slave)上执行该语句,得出输出如下

Empty set      

这样,你就出现了主从不一致性的问题!原因其实很简单,就是在master上执行的顺序为先删后插!而此时binlog为STATEMENT格式,它记录的顺序为先插后删!从(slave)同步的是binglog,因此从机执行的顺序和主机不一致!就会出现主从不一致!

如何解决? 解决方案有两种。

  • 隔离级别设为可重复读(Repeatable Read),在该隔离级别下引入间隙锁。当Session 1执行delete语句时,会锁住间隙。那么,Ssession 2执行插入语句就会阻塞住!
  • 将binglog的格式修改为row格式,此时是基于行的复制,自然就不会出现sql执行顺序不一样的问题!奈何这个格式在mysql5.1版本开始才引入。

因此由于历史原因,mysql将默认的隔离级别设为可重复读(Repeatable Read),保证主从复制不出问题!

4为什么大厂MySQL设置的隔离级别是RC

  • RC和RR的一个很大的区别是RR解决了不可重复读的问题。但是仔细想一想,不可重复读是问题吗?其实不是问题。我第一次读到的是1,再次读的时候为2,中间有人把1修改为2,那我读取到2就没问题。RC反应的是真实数据的变迁。主要数据真实有效(没提交就是脏读,无效),为什么怕被别人读出来呢?
  • RR下有间隙锁,使用锁就会导致资源的消耗和等待。

5MySQL主从复制的三种方式

5.1异步复制

为什么MySQL默认的隔离级别是RR而大厂使用的是RC?
  • Slave 端的 IO 进程连接上 Master,向 Master 请求指定日志文件的指定位置(或者从最开始的日志)之后的日志内容;
  • Master 接收到来自 Slave 的 IO 进程的请求后,负责复制的 IO 进程根据 Slave 的请求信息,读取相应日志内容,返回给Slave 的IO进程,并将本次请求读取的 bin-log 文件名及位置一起返回给 Slave 端 Slave 端的 IO
  • 进程接收到信息后,将接收到的日志内容依次添加到 Slave 端的 relay-log(中继日志) 文件的最末端,并将读取到的 Master端的 bin-log 的文件名和位置记录到 master-info 文件中,以便在下一次读取的时候能够清楚的告诉 Master:”我需要从某个 bin-log 的哪个位置开始往后的日志内容,请发给我”;
  • Slave 端的 Sql 进程检测到 relay-log(中继日志)中新增加了内容后,会马上解析 relay-log 的内容成为在 Master 端真实执行时候的那些可执行的内容,并在本地执行。

存在的问题:

主库的数据commit到binlog后则返回SUCCESS给客户端。因为binlog同步给slave是异步的,如果此时master提交而未同步给到slave,则数据发生丢失。

5.2半同步复制

为什么MySQL默认的隔离级别是RR而大厂使用的是RC?

主库在执行完客户端提交的事务后不是立刻返回给客户端,而是等待至少一个从库接收到并写到relay log中才返回给客户端。相对于异步复制,半同步复制提高了数据的安全性,同时它也造成了一定程度的延迟,这个延迟最少是一个TCP/IP往返的时间。所以,半同步复制最好在低延时的网络中使用。

首先明确一点:master上已提交,然后等待slave的ACK,最后返回给客户端结果。

存在的问题:

幻读:当用户发起一个事务,该事务已经写入redo日志和binlog日志,所以该数据为有效状态。在RC隔离级别下其他事务是可以读取到的。如果在等待slave的ack过程中binlog还没传输到slave上,则其他事务查询该数据为修改后的数据,此时master宕机。slave上升为master。此时在查询新master则查询不到数据,出现幻读。

数据丢失:提高数据的安全性,但不能完全避免数据丢失。

5.3增强半同步复制

为什么MySQL默认的隔离级别是RR而大厂使用的是RC?

现在我们已经知道,在半同步环境下,主库是在事务提交之后等待Slave ACK,所以才会有数据不一致问题。所以这个Slave ACK在什么时间去等待,也是一个很关键的问题了。因此MySQL针对半同步复制的问题,在5.7.2引入了Loss-less Semi-Synchronous,在调用binlog sync之后,engine层commit之前等待Slave ACK。这样只有在确认Slave收到事务events后,事务才会提交。

首先明确一点:在等待ack的时候,master状态为未提交。

6参考