拒绝空谈:实例演示MySQL事务隔离级别

欢迎关注微信公众号:互联网全栈架构

关系型数据库的事务有四个特性,其中一个就是隔离性(关于事务的更多信息,可以参考之前的一篇文章:漫谈MySQL中的事务)。为了满足不同的应用场景需求,有不同的隔离级别可供选择,在SQL标准和MySQL数据库中,共定义了四种隔离级别,分别是读未提交、读已提交、可重复读、串行化,隔离级别越低,系统的开销和并发通常就会更高,但同时也可能带来脏数据等问题。

今天我们就用实例来演示这四种隔离级别的具体情况,为了简便起见,我们创建一个结构非常简单的表,并插入一条数据以供测试:

CREATE TABLE `t_test` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE
=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `t_test` VALUES ('1''John');
一、读未提交

读未提交是最低的事务隔离级别,事务中的修改,即使没有提交,在其它事务中也是可见的,有时候也称为脏读,在实际开发中使用得很少:

拒绝空谈:实例演示MySQL事务隔离级别

先设置事务的隔离级别,事务一更新表中的唯一记录,并且确认更新成功,但事务还没有提交,也就是没有COMMIT,然后,执行事务二的SQL,但发现它已经可以读取事务一更新后的数据了。以下是事务一的SQL语句:
# 设置事务隔离级别为读未提交
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
# 开始事务
START TRANSACTION;
# 更新表中id=1的数据
UPDATE t_test SET name='Tom' WHERE id=1;
# 确认更新成功
SELECT * FROM t_test WHERE id=1;

接下来执行事务二中的SQL,发现它竟然读到了事务一中没有提交的更新:

SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
SELECT * FROM t_test WHERE id=1;
二、读已提交

读已提交可以避免脏读,但它有不可重复读的问题,就是两次读取的数据不一样:

拒绝空谈:实例演示MySQL事务隔离级别

事务一更新表中的数据,但还没有提交,这时候事务二去读取数据的话,结果就是更新前的数据,这样就防止了脏读,但如果事务一提交以后,事务二再次读到的就是更新以后的数据了,这样就出现了两次同样的查询,但结果却不一样的情况。

另外,为了演示方便,在SQL中加入了SLEEP函数,这样可以方便地让两个事务按照我们想要的顺序进行执行,关于SLEEP函数,在之前的文章也提到过,它能够让当前进程暂停执行一段时间,单位为秒。

事务一的SQL如下(在SQL中也加入了详细的注释):

SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
# 更新表中id=2的数据
UPDATE t_test SET name='Tom' WHERE id=1;
# 确认上面的UPDATE语句执行成功
SELECT * FROM t_test WHERE id=1;
# 让当前进程暂停5秒,让事务二开始执行,方便观察数据变化情况 
SELECT SLEEP(5) FROM dual;
COMMIT;

事务二的SQL如下:

SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
# 在事务一更新数据后、事务提交前查询数据,发现还是更新前的数据
SELECT * FROM t_test WHERE id=1;
# 暂停10秒,以便让事务一提交
SELECT SLEEP(10) FROM dual;
# 再次查询id=1的数据,发现已经变成Tom了,也就是事务一更新后的数据
SELECT * FROM t_test WHERE id=1;

我们先执行事务一的SQL,紧接着再执行事务二的SQL,由于加入了SLEEP函数,可以很方便地观察到:事务二不会读到事务一未提交的数据,但是同样的SQL查询,却出现了不同的结果,也就是存在不可重复读的问题。

三、可重复读

上面提到,对于读已提交的隔离级别,它不能解决不可重复读的问题,而可重复读则可以搞定,它保证在一次事务中多次读取同样记录的结果是一致的,然而,它不能解决幻读的情况:当前事务在读取某个范围内的数据时,其它事务又在此范围内插入了新的记录,前一个事务再次读取时会出现幻行。

由于可重复读相对复杂一些,它也是MySQL默认的事务隔离级别,我们分成两部分来进行演示:它可解决不可重复读的问题;存在幻读的现象。

首先来看可重复读的情况,也就是多次读取结果都一致:

拒绝空谈:实例演示MySQL事务隔离级别

可以看到,事务二读取到的数据总是更新前的数据,也就是多次读取的结果是一样的。事务一的SQL如下:

SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
UPDATE t_test SET name='Tom' WHERE id=1;
SELECT * FROM t_test WHERE id=1;
SELECT SLEEP(5) FROM dual;
COMMIT;
事务二的SQL:
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT * FROM t_test WHERE id=1;
SELECT SLEEP(10) FROM dual;
SELECT * FROM t_test WHERE id=1;
除了第一行设置事务隔离级别的语句不一样之外,其它部分与读已提交部分的示例一样,所以此处不再详细注释。当然,事务二的结果与读已提交的部分不一样,它两次读取id=1的数据,结果都是John,也就是更新后的数据。
对于可重复读,它不能避免幻读,对于这一部分,我们再用一个例子来进行说明,它产生幻读的示意图如下:

拒绝空谈:实例演示MySQL事务隔离级别

事务一插入id=2的数据,但还没有提交,这时候事务二读取不到id=2的记录,但紧接着插入id=2的数据又会报错

事务一的SQL如下:

# 设置事务隔离级别为可重复读
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
# 插入id=2的数据
INSERT INTO t_test VALUES(2'Jack');
# 确认上一步插入成功
SELECT * FROM t_test WHERE id=2;
# 让当前进程休眠5秒再提交,以观察事务二的执行情况
SELECT SLEEP(5) FROM dual;
COMMIT;
事务二的SQL:
# 设置事务隔离级别为可重复读
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
# 查询不到id=2的数据
SELECT * FROM t_test WHERE id=2;
# 但如果插入id=2的数据则会报错
INSERT INTO t_test VALUES(2'Jack');
COMMIT;
先执行事务一的SQL,再紧接着执行事务二的SQL,就会发现,在事务二中,虽然查询不到id=2的数据,但该事务向表中插入id=2的记录时却会报错(Duplicate Key),好像出现幻觉一样,所以可重复读存在幻读的问题。
四、串行化

串行化是最高的事务隔离级别,它强制事务按照顺序执行,从而可以避免前面提到的幻读问题,但它会在读取的每一行数据上都加锁,或者每次读取都在一个单独的事务中,总之,这种隔离级别会产生大量的加锁情况,效率很低,实际开发中也较少采用。

拒绝空谈:实例演示MySQL事务隔离级别

事务一的SQL:

# 设置事务隔离级别为串行化
SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
# 插入id=2的数据
INSERT INTO t_test VALUES(2'Jack');
# 确认上一步的操作成功
SELECT * FROM t_test WHERE id=2;
# 休眠5秒,方便观察
SELECT SLEEP(5) FROM dual;
COMMIT;

事务二的SQL:

# 设置事务隔离级别为串行化
SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
# 一直阻塞,直到事务一提交,然后读取到id=2的结果
SELECT * FROM t_test WHERE id=2;
COMMIT;

先执行事务一的SQL,紧接着执行事务二的SQL,会发现事务二一直阻塞,直到事务一提交,然后事务二可以读取到新插入的数据,从而避免了幻读的问题。

五、总结

MySQL的事务隔离级别是非常重要的概念,也是相对比较难的技术点,面试中也会经常问题,上面我们实例演示了这四种隔离级别的具体用法和效果,希望对朋友们有所帮助。上面的示例基于MySQL5.7版本,存储引擎为常用的InnoDB。下面用一个图来总结一下这几种隔离级别的情况:

拒绝空谈:实例演示MySQL事务隔离级别

创作不易,烦请点赞、分享,感谢!

鸣谢:
https://dev.mysql.com/doc/refman/5.7/en/
https://zhuanlan.zhihu.com/p/623298374


推荐阅读:

操控字节码:Javaassist介绍

Spring Boot Starter原理及实践

聊聊MySQL中的死锁

越俎代庖:应用广泛的代理模式

MySQL整数类型的长度到底是什么含义?

漫谈MySQL中的事务

臭名昭著,怙恶不悛的OOM,到底是什么?

原文始发于微信公众号(互联网全栈架构):拒绝空谈:实例演示MySQL事务隔离级别

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/173523.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!