MySQL事务隔离级别与相关SQL示例分析(脏读、不可重复读、幻读)

一、MySQL 事务隔离级别-面试相关话题

SQL 92 定义了四个隔离级别 (Isolation (database systems) - Wikipedia), 其隔离程度由高到低是:

  • 串行化(Serializable)
  • 可重复读(Repeatable reads)
  • 提交读(Read committed)
  • 未提交读(Read uncommitted)
MySQL事务隔离级别与相关SQL示例分析(脏读、不可重复读、幻读)

未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据。

提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)。

可重复读(Repeated Read):可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读。

串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。

专栏

Spring Cloud微服务架构项目实战

作者:软件架构

33.9币

40人已购

查看

当前主流数据库默认隔离级别:

MySQL->repeatable read;

Oracle、MS SQL Server ->read commited;


二、查看事务隔离级别及设置

SELECT @@global.tx_isolation, @@session.tx_isolation, @@tx_isolation;

MySQL事务隔离级别与相关SQL示例分析(脏读、不可重复读、幻读)

1.查看当前会话隔离级别

select @@session.tx_isolation;

2.查看系统当前隔离级别

select @@global.tx_isolation;

3.设置当前会话隔离级别

set session transaction ISOLATION LEVEL REPEATABLE READ;

set session transaction ISOLATION LEVEL READ COMMITTED;

MySQL事务隔离级别与相关SQL示例分析(脏读、不可重复读、幻读)

4.设置系统当前隔离级别

set global transaction isolation level repeatable read;


@@tx_isolation -- 默认的行为(不带session和global)是为下一个(未开始)事务设置隔离级别。

专栏

SkyWalking分布式链路追踪和监控

作者:软件架构

33.9币

50人已购

查看


三、为什么MySQL用的是Repeatable Read而不是Read Committed?

MySQL binlog的格式三种:statement、row、mixed;

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


那MySQL在5.0这个版本或以前,binlog只支持STATEMENT这种格式!而这种格式在读已提交(Read Commited)这个隔离级别下主从复制是有bug的,因此Mysql将可重复读(Repeatable Read)作为默认的隔离级别!

在MySQL 5.1版本开始,binlog支持row格式。可以将binglog的格式修改为row格式,此时是基于行的复制,自然就不会出现SQL执行顺序不一样的问题!因此由于历史原因,MySQL将默认的隔离级别设为可重复读(Repeatable Read),保证主从复制不出问题。

专栏

Elasticsearch 7.x订单中心实战

作者:软件架构

33.9币

100人已购

查看


四、Read Commited和Repeatable Read比较

先了解一下Read Commited和Repeatable Read差异:

1. Read Committed

想要避免脏读,最简单的方式就是在事务更新操作上加一把写锁, 其他事务需要读取数据时候,需要等待这把写锁释放。


MySQL事务隔离级别与相关SQL示例分析(脏读、不可重复读、幻读)


如上图所示,Transaction 1 在写操作时候,对数据 A 加了写锁, 那么 Transaction 2 想要读取 A,就必须等待这把锁释放。 这样就避免当前事务读取其他事务的未提交数据。

但是除了脏读,一致性的要求还需要「可重复读」,即 「在一个事务内,多次读取的特定数据都必须是一致的 (即便在这过程中该数据被其他事务修改)」。


MySQL事务隔离级别与相关SQL示例分析(脏读、不可重复读、幻读)

上图就是没能保证「可重复度」,Transaction 2 第一次读取到了数据 A, 然后 Transaction 1 对数据 A 更新到 A',那么当 Tranction 2 再次读取 A 时候, 它本来期望读到 A,但是却读到了 A',这和它的预期不相符了。 解决这个问题,就需要提升隔离级别到「Repeatable Read」。

专栏

Kafka v2.3 快速入门与实践

作者:软件架构

33.9币

53人已购

查看


2. Repeatable Read

这个名字非常容易理解,即保障在一个事务内重复读取时, 始终能够读取到相同的内容。来看图:

MySQL事务隔离级别与相关SQL示例分析(脏读、不可重复读、幻读)


如上所示,当 Transation 2 读取 A 时候,会同时加上一把 Read Lock, 这把锁会阻止 Transaction 1 将 A 更新为 A',Transaction 1 要么选择等待, 要么就选择结束。


五、互联网项目为什么将隔离级别设为读已提交(Read Commited)?

创建一张测试表:

CREATE TABLE `test` (

`id` int(11) NOT NULL,

`color` varchar(20) NOT NULL,

PRIMARY KEY (`id`)

) ENGINE=InnoDB


测试数据:

insert into test(id, color) values(1, 'red');

insert into test(id, color) values(2, 'white');

insert into test(id, color) values(5, 'red');

insert into test(id, color) values(7, 'white');

MySQL事务隔离级别与相关SQL示例分析(脏读、不可重复读、幻读)

1. 在Repeatable Read隔离级别下,存在间隙锁,导致出现死锁的几率比Read Commited高。

执行如下脚本:

select * from test where id <3 for update;

在Repeatable Read隔离级别下,存在间隙锁,可以锁住(2,5)这个间隙,防止其他事务插入数据。

而在Read Commited隔离级别下,不存在间隙锁,其他事务是可以插入数据。在Read Commited隔离级别下并不是不会出现死锁,只是出现几率比Repeatable Read低而已。

专栏

Hadoop v3.1大数据技术快速入门

作者:软件架构

33.9币

23人已购

查看


2. 在Repeatable Read隔离级别下,条件列未命中索引会锁表!而在Read Commited隔离级别下,只锁行。

执行语句:

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

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

MySQL事务隔离级别与相关SQL示例分析(脏读、不可重复读、幻读)

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

实际加锁如下:

MySQL事务隔离级别与相关SQL示例分析(脏读、不可重复读、幻读)

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

MySQL事务隔离级别与相关SQL示例分析(脏读、不可重复读、幻读)

3. 在Read Commited隔离级别下,半一致性读(semi-consistent)特性增加了update操作的并发性。

在MySQL v5.1.15的时候,innodb引入了一个概念叫做“semi-consistent”,减少了更新同一行记录时的冲突,减少锁等待。

所谓半一致性读就是,一个update语句,如果读到一行已经加锁的记录,此时InnoDB返回记录最近提交的版本,由MySQL上层判断此版本是否满足update的where条件。若满足(需要更新),则MySQL会重新发起一次读操作,此时会读取行的最新版本(并加锁)。

专栏

Elasticsearch 7.x从入门到精通

作者:软件架构

33.9币

112人已购

查看


具体表现如下:

此时有两个Session,Session1和Session2。

Session1执行:update test set color = 'blue' where color = 'red'; 先不Commit事务。

与此同时Session2执行:update test set color = 'blue' where color = 'white';


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

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


六、读未提交(Read Uncommitted)和串行化(Serializable)隔离级别

项目中一般不采用读未提交(Read Uncommitted)和串行化(Serializable)两个隔离级别,原因如下:

1. 采用读未提交(Read Uncommitted),一个事务读到另一个事务未提交读数据,存在脏读(Dirty Read)。

2. 采用串行化(Serializable),每次读操作都会加锁,快照读失效,一般是使用MySQL自带分布式事务功能时才使用该隔离级别。

这个是XA事务,是强一致性事务,性能不佳。互联网的分布式方案,多采用最终一致性的事务解决方案。


在Read Commited级别下,不可重复读问题需要解决么?

不用解决,这个问题是可以接受的!毕竟你数据都已经提交了,读出来本身就没有太大问题!Oracle的默认隔离级别就是Read Commited。


在Read Commited级别下,主从复制用什么binlog格式?

在该隔离级别下,用的binlog为row格式,是基于行的复制。Innodb的创始人也是建议binlog使用该格式。


分享到:


相關文章: