为什么
先看另一篇博客,可重复读其实没啥必要。
而且一般的DBMS系统,默认都会使用读提交(Read-Comitted,RC)作为默认隔离级别,如Oracle、SQL Server等,而MySQL却使用可重复读(Read-Repeatable,RR)。要知道,越高的隔离级别,能解决的数据一致性问题越多,理论上性能的损耗更大,且并发性越低。隔离级别依次为: SERIALIZABLE > RR > RC > RU
为什么mysql要这样呢
-
在读已提交(Read Commited)级别下,出现不可重复读问题怎么办?需要解决么?
不用解决,这个问题是可以接受的!毕竟你数据都已经提交了,读出来本身就没有太大问题!Oracle ,SqlServer 默认隔离级别就是RC,我们也没有更改过它的默认隔离级别. -
在Oracle,SqlServer中都是选择读已提交(Read Commited)作为默认的隔离级别,为什么Mysql不选择读已提交(Read Commited)作为默认隔离级别,而选择可重复读(Repeatable Read)作为默认的隔离级别呢?
历史原因,早阶段Mysql(5.1版本之前)的Binlog类型Statement是默认格式,即依次记录系统接受的SQL请求;5.1及以后,MySQL提供了Row,Mixed,statement 3种Binlog格式, 当binlog为statement格式,使用RC隔离级别时,会出现BUG因此Mysql将可重复读(Repeatable Read)作为默认的隔离级别!
Binlog简介
Mysql binlog是二进制日志文件,用于记录mysql的数据更新或者潜在更新(比如DELETE语句执行删除而实际并没有符合条件的数据),在mysql主从复制中就是依靠的binlog。可以通过语句“show binlog events in ‘binlogfile’”来查看binlog的具体事件类型。binlog记录的所有操作实际上都有对应的事件类型的
MySQL binlog的三种工作模式: Row(用到MySQL的特殊功能如存储过程、触发器、函数,又希望数据最大化一直则选择Row模式,我们公司选择的是row) 简介:日志中会记录每一行数据被修改的情况,然后在slave端对相同的数据进行修改。 优点:能清楚的记录每一行数据修改的细节 缺点:数据量太大
Statement (默认) 简介:每一条被修改数据的sql都会记录到master的bin-log中,slave在复制的时候sql进程会解析成和原来master端执行过的相同的sql再次执行。在主从同步中一般是不建议用statement模式的,因为会有些语句不支持,比如语句中包含UUID函数,以及LOAD DATA IN FILE语句等 优点:解决了 Row level下的缺点,不需要记录每一行的数据变化,减少bin-log日志量,节约磁盘IO,提高新能 缺点:容易出现主从复制不一致
Mixed(混合模式) 简介:结合了Row level和Statement level的优点,同时binlog结构也更复杂。
我们可以简单理解为binlog是一个记录数据库更改的文件,主从复制时需要此文件,具体细节先略过
主从不一致实操
binlog为STATEMENT格式,且隔离级别为读已提交(Read Commited)时,有什么bug呢? 测试表:
mysql> select * from test;
±—±-----±-----+
| id | name | age |
±—±-----±-----+
| 1 | NULL | NULL |
| 2 | NULL | NULL |
| 3 | NULL | NULL |
| 4 | NULL | NULL |
| 5 | NULL | NULL |
| 6 | NULL | NULL |
±—±-----±-----+
6 rows in set (0.00 sec)
Master此时输出
select * from test;
±—±-----±-----+
| id | name | age |
±—±-----±-----+
| 7 | name | 100 |
±—±-----±-----+
1 row in set (0.00 sec)
但是,你在此时在从(slave)上执行该语句,得出输出
mysql> select * from test;
Empty set (0.00 sec)
在master上执行的顺序为先删后插!而此时binlog为STATEMENT格式,是基于事务记录,在事务未提交前,二进制日志先缓存,提交后再写入记录的,因此顺序为先插后删!slave同步的是binglog,因此从机执行的顺序和主机不一致!slave在插入后删除了所有数据.
图中是正确的展示结果。假如RC模式下采用的是statment,binlog的记录顺序是以commit方式提交的,本实验中都默认是自动commit。
statement下伪日志记录:会话二:
update t set d=5 where id=0;
commit;
会话一:
begin;
update t set d=100 where d=5;
commit;通过上面解析出来的binlog执行就有问题了,最终结果是2行数据d变成了100,明显与期望不一致。如果是row格式,那么伪日志记录如下:同样开始也是会话二:
update t where
id=0
c=0
d=0
set
id=0
c=0
d=5
commit;
会话一:
begin;
update t set
id=5
c=5
d=5
set
id=5
c=5
d=100
commit;看出来了吗,显然row格式记录方式按照这个binlog执行明显是正确的,也符合预期。因为row格式会记录每一列数据的变化前后值,匹配更精准。那RR模式为什么statment不会有问题呢,因为RR模式下会锁全表,会话2必须等会话1释放x锁后才能执行,自然也不会出问题。
作者:lynn.wu
链接:https://www.zhihu.com/question/344037151/answer/1043568963
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
解决方案有两种! (1)隔离级别设为可重复读(Repeatable Read),在该隔离级别下引入间隙锁。当Session 1执行delete语句时,会锁住间隙。那么,Ssession 2执行插入语句就会阻塞住! (2)将binglog的格式修改为row格式,此时是基于行的复制,自然就不会出现sql执行顺序不一样的问题!奈何这个格式在mysql5.1版本开始才引入。因此由于历史原因,mysql将默认的隔离级别设为可重复读(Repeatable Read),保证主从复制不出问题!
简单来说
历史原因:早期MySQL的binlog日志只有statement格式,在读已提交的隔离界别下,binlog日志存在bug,会导致主从复制不一致的情况。因此默认的隔离级别使用可重复读。
bug:binlog日志中记录的语句顺序和原有顺序会不一致。先删除后插入的操作,同步导从库就变成了先插入后删除的操作了。
MySQL5.1版本之后binlog日志格式还支持了row,mixed ,通常使用读已提交 + row 可以实现更高的并发。对于其中的幻读、不可重复读问题在业务层做保护即可。
binlog的statement模式不适合做主从复制。比如一些函数每次执行结果会不一致。uuid()等。
RC和 RR用哪个
此时我们纠结的应该就只有一个问题了:隔离级别是用读已提交还是可重复读?
接下来对这两种级别进行对比的第一种情况:
在RR隔离级别下,存在间隙锁,导致出现死锁的几率比RC大的多!
实现一个简单的间隙锁例子
select * from test where id <11 ;
±—±-----±-----+
| id | name | age |
±—±-----±-----+
| 1 | NULL | NULL |
| 2 | NULL | NULL |
| 3 | NULL | NULL |
| 4 | NULL | NULL |
| 5 | NULL | NULL |
| 6 | NULL | NULL |
| 7 | name | 7 |
±—±-----±-----+
7 rows in set (0.00 sec)
在RR隔离级别下,可以锁住(-∞,10] 这个间隙,防止其他事务插入数据! 而在RC隔离级别下,不存在间隙锁,其他事务是可以插入数据!
ps:在RC隔离级别下并不是不会出现死锁,只是出现几率比RR低而已
锁表和锁行
在RR隔离级别下,条件列未命中索引会锁表!而在RC隔离级别下,只锁行
select * from test;
±—±-----±-----+
| id | name | age |
±—±-----±-----+
| 8 | name | 11 |
| 9 | name | 9 |
| 10 | name | 15 |
| 11 | name | 15 |
| 12 | name | 16 |
±—±-----±-----+
锁表的例子:
session2插入失败 查询 数据显示:
select * from test;
±—±-----±-----+
| id | name | age |
±—±-----±-----+
| 8 | name | 11 |
| 9 | name | 9 |
| 10 | name | 16 |
| 11 | name | 16 |
| 12 | name | 16 |
±—±-----±-----+
半一致性读(semi-consistent)特性
在RC隔离级别下,半一致性读(semi-consistent)特性增加了update操作的并发性!
在5.1.15的时候,innodb引入了一个概念叫做“semi-consistent”,减少了更新同一行记录时的冲突,减少锁等待。 所谓半一致性读就是,一个update语句,如果读到一行已经加锁的记录,此时InnoDB返回记录最近提交的版本,判断此版本是否满足where条件。若满足则重新发起一次读操作,此时会读取行的最新版本并加锁!
建议
在RC级别下,binlog用row格式,是基于行的复制,Innodb的创始人也是建议binlog使用该格式(mix格式可以么)
互联网项目请用:读已提交(Read Commited)这个隔离级别