mysql的默认隔离级别

引言

开始咱们的内容,相信你们必定遇到过下面的一个面试场景mysql

面试官:“讲讲mysql有几个事务隔离级别?”
你:“读未提交,读已提交,可重复读,串行化四个!默认是可重复读”
面试官:“为何mysql选可重复读做为默认的隔离级别?”
(你面露苦色,不知如何回答!)
面试官:"大家项目中选了哪一个隔离级别?为何?"
你:“固然是默认的可重复读,至于缘由。。呃。。。”
(而后你就能够回去等通知了!)
面试

为了不上述尴尬的场景,请继续往下阅读!
Mysql默认的事务隔离级别是可重复读(Repeatable Read),那互联网项目中Mysql也是用默认隔离级别,不作修改么?
OK,不是的,咱们在项目中通常用读已提交(Read Commited)这个隔离级别!
what!竟然是读已提交,网上不是说这个隔离级别存在不可重复读幻读问题么?不用管么?好,带着咱们的疑问开始本文!sql

正文

咱们先来思考一个问题,在Oracle,SqlServer中都是选择读已提交(Read Commited)做为默认的隔离级别,为何Mysql不选择读已提交(Read Commited)做为默认隔离级别,而选择可重复读(Repeatable Read)做为默认的隔离级别呢?
数据库

Why?Why?Why?

这个是有历史缘由的,固然要从咱们的主从复制开始讲起了!
主从复制,是基于什么复制的?
是基于binlog复制的!这里不想去搬binlog的概念了,就简单理解为binlog是一个记录数据库更改的文件吧~
binlog有几种格式?
OK,三种,分别是session

  • statement:记录的是修改SQL语句
  • row:记录的是每行实际数据的变动
  • mixed:statement和row模式的混合

那Mysql在5.0这个版本之前,binlog只支持STATEMENT这种格式!而这种格式在读已提交(Read Commited)这个隔离级别下主从复制是有bug的,所以Mysql将可重复读(Repeatable Read)做为默认的隔离级别!
接下来,就要说说当binlog为STATEMENT格式,且隔离级别为读已提交(Read Commited)时,有什么bug呢?以下图所示,在主(master)上执行以下事务

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

select * from test;

输出以下分布式

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

可是,你在此时在从(slave)上执行该语句,得出输出以下性能

Empty set

这样,你就出现了主从不一致性的问题!缘由其实很简单,就是在master上执行的顺序为先删后插!而此时binlog为STATEMENT格式,它记录的顺序为先插后删!从(slave)同步的是binglog,所以从机执行的顺序和主机不一致!就会出现主从不一致!
如何解决?
解决方案有两种!
(1)隔离级别设为可重复读(Repeatable Read),在该隔离级别下引入间隙锁。当Session 1执行delete语句时,会锁住间隙。那么,Ssession 2执行插入语句就会阻塞住!
(2)将binglog的格式修改成row格式,此时是基于行的复制,天然就不会出现sql执行顺序不同的问题!奈何这个格式在mysql5.1版本开始才引入。所以因为历史缘由,mysql将默认的隔离级别设为可重复读(Repeatable Read),保证主从复制不出问题!优化

那么,当咱们了解完mysql选可重复读(Repeatable Read)做为默认隔离级别的缘由后,接下来咱们将其和读已提交(Read Commited)进行对比,来讲明为何在互联网项目为何将隔离级别设为读已提交(Read Commited)ui

对比

ok,咱们先明白一点!项目中是不用读未提交(Read UnCommitted)串行化(Serializable)两个隔离级别,缘由有二

  • 采用读未提交(Read UnCommitted),一个事务读到另外一个事务未提交读数据,这个不用多说吧,从逻辑上都说不过去!
  • 采用串行化(Serializable),每一个次读操做都会加锁,快照读失效,通常是使用mysql自带分布式事务功能时才使用该隔离级别!(笔者从未用过mysql自带的这个功能,由于这是XA事务,是强一致性事务,性能不佳!互联网的分布式方案,多采用最终一致性的事务解决方案!)

也就是说,咱们该纠结都只有一个问题,究竟隔离级别是用读已经提交呢仍是可重复读?
接下来对这两种级别进行对比,讲讲咱们为何选读已提交(Read Commited)做为事务隔离级别!
假设表结构以下

CREATE TABLE `test` ( `id` int(11) NOT NULL, `color` varchar(20) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB

数据以下

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

为了便于描述,下面将

  • 可重复读(Repeatable Read),简称为RR;
  • 读已提交(Read Commited),简称为RC;

原因一:在RR隔离级别下,存在间隙锁,致使出现死锁的概率比RC大的多!
此时执行语句

select * from test where id <3 for update;

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

ps:在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'; 

session 2尝试加锁的时候,发现行上已经存在锁,InnoDB会开启semi-consistent read,返回最新的committed版本(1,red),(2,white),(5,red),(7,white)。MySQL会从新发起一次读操做,此时会读取行的最新版本(并加锁)!
而在RR隔离级别下,Session2只能等待!

两个疑问

在RC级别下,不可重复读问题须要解决么?
不用解决,这个问题是能够接受的!毕竟你数据都已经提交了,读出来自己就没有太大问题!Oracle的默认隔离级别就是RC,大家改过Oracle的默认隔离级别么?

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

总结

本文啰里八嗦了一篇文章只是为了说明一件事,互联网项目请用:读已提交(Read Commited)这个隔离级别!

 

做者:孤独烟 出处: http://rjzheng.cnblogs.com/

相关文章
相关标签/搜索