1.mysql的主从复制

  1. 主服务器上面的任何操作都会通过自己的 I/O tread(I/O 线程)保存在二进制日志 Binary log 里面。
  2. 从服务器上面也启动一个 I/O thread,通过配置好的用户名和密码, 连接到主服务器上面请求读取二进制日志,然后把读取到的二进制日志写到本地的一个Realy log(中继日志)里面。
  3. 从服务器上面同时开启一个 SQL thread 定时检查 Realy log(这个文件也是二进制的),如果发现有更新立即把更新的内容在本机的数据库上面执行一遍。
    具体复制过程参考: MySQL主从复制原理

2.当mysql隔离级别为“读提交”时

在Master库上,首先通过命令,查询一下默认隔离级别

show variables like 'transaction_isolation';

设置session隔离级别为“读提交”

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

在读提交的隔离级别下开启两个会话:按上面的顺序执行命令。
image.png

最后在Master主库上有一条记录,而在Slave从库上没有数据。

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

在mysql5.1以前mysql的逻辑操作日志binlog默认的是statement模式,用于恢复和复制。主从复制的binlog的采用的是statement方式。主库就是将每次数据库的sql修改(增删改)在提交前以二进制编码的形式保存到日志文件中。从库定时从主库的日志文件复制到本地日志,从库根据本地日志(继中日志)的变化执行sql语句。

3.当mysql默认隔离级别为可重复读时

开启两个会话,左边为session1,右边为session2,按数字所示顺序执行
image.png

隔离级别设为可重复读(Repeatable Read),在该隔离级别下引入间隙锁。当Session1执行delete语句时,会锁住间隙。那么,Ssession2执行插入语句就会阻塞住,无法继续执行。只有到session1 提交(commit)了之后。才能执行。实现了写入binlog的语句串行化。解决了主从不一致的问题。

当然,还有就是可以将binglog的格式修改为row格式,此时是基于行的复制,自然就不会出现sql执行顺序不一样的问题!奈何这个格式在mysql5.1版本开始才引入。因此由于历史原因,mysql将默认的隔离级别设为可重复读(Repeatable Read),保证主从复制不出问题!

关于日志相关问题,请参考《MySQL三大日志》

4.为什么MySQL要用可重复读作为默认隔离级别

MySQL使用可重复读来作为默认隔离级别的主要原因是语句级的Binlog。可重复读能提供SQL语句的写可串行化,保证了主从一致。

5.为什么在互联网项目为什么将隔离级别设为读已提交

当我们了解完mysql选可重复读(Repeatable Read)作为默认隔离级别的原因后,接下来我们将其和读已提交(Read Commited)进行对比,来说明为什么在互联网项目为什么将隔离级别设为读已提交(Read Commited)!

假设我们有这么一张表

+----+-------+
| id | color |
+----+-------+
|  1 |  red  |
|  2 | white |
|  5 |  red  |
|  7 | white |
+----+-------+

缘由一:在RR隔离级别下,存在间隙锁,导致出现死锁的几率比RC大的多
此时执行语句

select * from test where id <3 for update;

在RR隔离级别下,存在间隙锁,可以锁住(2,5)这个间隙,防止其他事务插入数据
而在RC隔离级别下,不存在间隙锁,其他事务是可以插入数据

在RC隔离级别下并不是不会出现死锁,只是出现几率比RR低而已!

缘由二:在RR隔离级别下,条件列未命中索引会锁表!而在RC隔离级别下,只锁行
此时执行语句

update test set color = 'blue' where color = 'white'; 

在RC隔离级别下,其先走聚簇索引,进行全部扫描。加锁如下

但在实际中,MySQL做了优化,在MySQL Server过滤条件,发现不满足后,会调用unlock_row方法,把不满足条件的记录放锁。
实际加锁如下

然而,在RR隔离级别下,走聚簇索引,进行全部扫描,最后会将整个表锁上,如下所示

缘由三:在RC隔离级别下,引入半一致性读(semi-consistent)特性增加了update操作的性能

在5.1.15的时候,innodb引入了一个概念叫做“semi-consistent”,减少了更新同一行记录时的冲突,减少锁等待。
所谓半一致性读就是,一个update语句,如果读到一行已经加锁的记录,此时InnoDB返回记录最近提交的版本,由MySQL上层判断此版本是否满足update的where条件。若满足(需要更新),则MySQL会重新发起一次读操作,此时会读取行的最新版本(并加锁)
具体表现如下:

此时有两个Session,Session1和Session2。Session1执行

update test set color = 'blue' where color = 'red'; 

先不Commit事务,与此同时Ssession2执行

update test set color = 'blue' where color = 'white'; 

session2尝试加锁的时候,发现行上已经存在锁,InnoDB会开启semi-consistent read,返回最新的committed版本。MySQL会重新发起一次读操作,此时会读取行的最新版本(并加锁)

而在RR隔离级别下,Session2只能等待。

6.总结

  • 在RC级别下,不可重复读问题需要解决么?
    不用解决,这个问题是可以接受的!毕竟你数据都已经提交了,读出来本身就没有太大问题!Oracle和Postgressql的默认隔离级别就是RC,你们改过他们的默认隔离级别么?

  • 在RC级别下,主从复制用什么binlog格式?
    OK,在该隔离级别下,用的binlog为row格式,是基于行的复制!Innodb的创始人也是建议binlog使用该格式!

mysql默认隔离级别为什么是可重复读
互联网项目中mysql应该选什么事务隔离级别