Mysql默认隔离级别为什么是可重复读?

本文深入探讨MySQL事务隔离级别的选择,对比可重复读与读已提交的区别,分析各自在主从复制、死锁概率、锁机制及并发性能上的表现,揭示互联网项目为何偏爱读已提交。

知识点总结

1.数据库默认隔离级别: mysql —可重复读; oracle,postgres —已提交读

2.mysql binlog的格式三种:statement,row,mixed

3.为什么mysql用的是可重复读而不是read committed:在 5.0之前只有statement一种格式,而主从复制存在了大量的不一致,故选用repeatable

4.为什么默认的隔离级别都会选用read commited 原因有三:repeatable存在间隙锁会使死锁的概率增大;在可重复读隔离级别下,条件列未命中索引会锁表!而在已提交读隔离级别下,只锁行;在已提交读隔离级别下,引入了半一致性读(semi-consistent)特性增加了update操作的并发性能。更重要的是不可重复读问题在开发中是可以接收的,毕竟你数据都已经提交了,读出来本身就没有太大问题

2.在已提交读级别下,主从复制用什么binlog格式:row格式,是基于行的复制!

引言

开始我们的内容,相信大家一定遇到过下面的一个面试场景

面试官:“讲讲mysql有几个事务隔离级别?”
你:“读未提交,读已提交,可重复读,串行化四个!默认是可重复读”
面试官:“为什么mysql选可重复读作为默认的隔离级别?”
(你面露苦色,不知如何回答!)
面试官:"你们项目中选了哪个隔离级别?为什么?"
你:“当然是默认的可重复读,至于原因。。呃。。。”
(然后你就可以回去等通知了!)

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

正文

我们先来思考一个问题,在Oracle,SqlServer,postgresql中都是选择读已提交作为默认的隔离级别,为什么Mysql不选择Read Commited作为默认隔离级别,而选择Repeatable Read作为默认的隔离级别呢?
Why?Why?Why?
这个是有历史原因的,当然要从我们的主从复制开始讲起了!
主从复制,是基于什么复制的?
是基于binlog复制的!这里不想去搬binlog的概念了,就简单理解为binlog是一个记录数据库更改的文件吧~
binlog有几种格式?
OK,三种,分别是

  • 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)!

对比
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和Postgressql的默认隔离级别就是RC,你们改过他们的默认隔离级别么?

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

总结

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

<think>好的,我需要回答用户关于MySQL默认事务隔离级别及其原因的问题。首先,我得确认默认级别是什么。根据提供的引用,多个来源提到默认是“可重复”(Repeatable Read)[^1][^2][^4]。接下来,用户想知道为什么选择这个级别。 引用[3]和[^3]指出,可重复解决了主从复制中的一致性问题,特别是在binlog记录顺序上。使用间隙锁和临键锁防止幻,确保事务B的插入操作被阻塞,保证事务的顺序正确。这样在基于语句的复制中不会出现数据不一致。 另外,引用[5]提到可重复通过MVCC(多版本并发控制)实现高并发,避免了共享锁的开销,平衡了一致性和性能。这也是选择它的原因之一。 需要将这些信息整理成结构清晰的回答,分点说明默认级别、原因,并引用相关资料。同时生成相关问题,如其他隔离级别、优缺点比较等。确保使用正确的中文表达和LaTeX格式,比如事务隔离级别的名称用斜体或引号,数学表达式用$包裹。</think>### MySQL 默认事务隔离级别及原因分析 #### 1. MySQL 默认事务隔离级别 MySQL默认事务隔离级别是**可重复(Repeatable Read)**[^1][^2][^4]。这是 InnoDB 存储引擎的默认配置,符合 SQL 标准中的定义,能够消除不可重复(Non-Repeatable Read),并通过多版本并发控制(MVCC)和锁机制解决幻(Phantom Read)问题[^4]。 --- #### 2. 为什么选择可重复? ##### 2.1 主从复制与 binlog 一致性 在 MySQL 的主从复制架构中,基于语句的复制(Statement-Based Replication, SBR)需要保证事务操作的顺序在从库上严格一致。如果隔离级别为**已提交(Read Committed)**,可能出现以下问题: - 事务 A 删除某范围数据时,事务 B 可能在同一时间插入新数据并先提交。 - 由于 binlog 按提交顺序记录,可能导致从库先记录删除操作再记录插入操作,最终数据被意外删除[^3]。 **可重复**通过**间隙锁(Gap Locks)**和**临键锁(Next-Key Locks)**阻止其他事务在锁定范围内插入数据,确保事务操作的逻辑顺序与 binlog 记录一致[^3][^5]。 ##### 2.2 平衡性能与一致性 - **高并发支持**:可重复使用 MVCC 机制生成数据快照,避免共享锁的频繁竞争,提升并发性能[^5]。 - **解决幻**:虽然 SQL 标准中可重复允许幻,但 InnoDB 通过锁机制消除了这一问题[^4]。 ##### 2.3 历史兼容性 早期 MySQL 版本选择可重复作为默认级别,后续版本延续了这一设计以减少升级带来的兼容性问题。 --- #### 3. 不同隔离级别的对比 | 隔离级别 | 脏 | 不可重复 | 幻 | 性能开销 | |-------------------|------|------------|------|----------| | 未提交 | 是 | 是 | 是 | 低 | | 已提交 | 否 | 是 | 是 | 中 | | **可重复** | 否 | 否 | 否* | 中高 | | 串行化 | 否 | 否 | 否 | 高 | > *InnoDB 通过锁机制解决了幻问题[^4]。 --- #### 4. 如何设置隔离级别? ```sql -- 设置全局隔离级别 SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ; -- 设置当前会话隔离级别 SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; ``` --- 相关问题
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值