MySQL事务隔离级别解密

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: READ UNCOMMITTED 未提交读  在READ UNCOMMITTED级别,事务中的修改,即使没有提交,对其他事务也都是可见的。
  1. READ UNCOMMITTED 未提交读 
    在READ UNCOMMITTED级别,事务中的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,这也被称作脏读。这个级别会导致很多问题,从性能上来说,READ UNCOMMITTED不会比其他的级别好太多,但确缺乏其他级别的很多好处,除非真的有必要的理由,在实际应用中一般很少使用。
  2. READ COMMITED 提交读 
    大多数数据库系统的默认隔离级别是 READ COMMITED(但mysql不是)。READ COMMITED满足前面提到的隔离性简单定义:一个事务开始时,只能看见已经提交的事务所做的修改。换句话说,一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。这个级别有时候也叫做不可重复读,因为两次执行同样的查询,可能会得到不一样的结果。
  3. REPEATABLE READ 可重复写 
    MySQL的默认事务隔离级别。 
    REPEATABLE READ解决了脏读的问题。该级别保证了在同一个事务中多次读取同样的记录的结果是一致的。但是理论上,可重复读隔离级别还是无法解决另一个幻读的问题。所谓幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行。InnoDB存储引擎通过多版本并发控制(MVCC)解决了幻读的问题。
  4. SERIALIZABLE 可串行化 
    SERIALIZABLE 是最高的隔离级别。它通过强事务串行执行,避免了前面说的幻读的问题。简单来说,SERIALIZABLE会在曲度的每一行数据都加上锁,所以可能导致大量的超时和锁争用的问题。实际应用中也很少用到这个隔离级别,只有在非常需要确保数据一致性而且可以接受没有并发的情况下,才考虑使用该级别。

实验

上面是摘自《高性能MySQL》一书,光这么看,可能也理解不到多少东西,不如直接做实验亲身体验一下,加深理解。

我们先简单创建一个表 users,结构如下:

+------------+------------------+------+-----+-------------------+----------------+
| Field      | Type             | Null | Key | Default           | Extra          |
+------------+------------------+------+-----+-------------------+----------------+
| id         | int(11) unsigned | NO   | PRI | <null>            | auto_increment |
| name       | varchar(50)      | NO   |     |                   |                |
| created_at | datetime         | NO   |     | CURRENT_TIMESTAMP |                |
| updated_at | datetime         | NO   |     | CURRENT_TIMESTAMP |                |
| deleted_at | datetime         | YES  |     | <null>            |                |
+------------+------------------+------+-----+-------------------+----------------+

实现插入三条数据:

+----+-------------+---------------------+---------------------+------------+
| id | name        | created_at          | updated_at          | deleted_at |
+----+-------------+---------------------+---------------------+------------+
| 1  | coolcao2018 | 2018-05-28 12:48:43 | 2018-05-28 12:48:43 | <null>     |
| 2  | tom         | 2018-05-28 13:24:28 | 2018-05-28 13:24:28 | <null>     |
| 3  | lili        | 2018-05-31 08:54:28 | 2018-05-31 08:54:28 | <null>     |
+----+-------------+---------------------+---------------------+------------+

然后打开两个终端,A和B分别表示两个用户同时在操作。

READ UNCOMMITTED

对于用户A,操作:

mysql root@localhost:test> set session transaction isolation level read uncommitted;
mysql root@localhost:test> start transaction;
mysql root@localhost:test> select * from users;
+----+-------------+---------------------+---------------------+------------+
| id | name        | created_at          | updated_at          | deleted_at |
+----+-------------+---------------------+---------------------+------------+
| 1  | coolcao2018 | 2018-05-28 12:48:43 | 2018-05-28 12:48:43 | <null>     |
| 2  | tom         | 2018-05-28 13:24:28 | 2018-05-28 13:24:28 | <null>     |
| 3  | lili        | 2018-05-31 08:54:28 | 2018-05-31 08:54:28 | <null>     |
+----+-------------+---------------------+---------------------+------------+

然后,用户B操作,

mysql root@localhost:test> set session transaction isolation level read uncommitted;
mysql root@localhost:test> start transaction;
mysql root@localhost:test> update users set name='coolcao' where id=1;

此时,用户B并未提交事务,用户A进行查询操作看看:

mysql root@localhost:test> select * from users;
+----+---------+---------------------+---------------------+------------+
| id | name    | created_at          | updated_at          | deleted_at |
+----+---------+---------------------+---------------------+------------+
| 1  | coolcao | 2018-05-28 12:48:43 | 2018-05-28 12:48:43 | <null>     |
| 2  | tom     | 2018-05-28 13:24:28 | 2018-05-28 13:24:28 | <null>     |
| 3  | lili    | 2018-05-31 08:54:28 | 2018-05-31 08:54:28 | <null>     |
+----+---------+---------------------+---------------------+------------+

从整个过程来看,用户B还并未提交事务,但是A却已经能够直接读到B的更新。

从上面实验结果来看,不难理解上面对于 READ UNCOMMITTED级别的描述: 在READ UNCOMMITTED级别,事务中的修改,即使没有提交,对其他事务也都是可见的。

READ COMMITTED

同时将A,B两个终端的事务级别设置为 read committed:

// 在A,B两个终端都执行
set session transaction isolation level read committed;

对于A,我们开启一个事务,然后更新一下数据,但并不提交事务:

mysql root@localhost:test>  set session transaction isolation level read committed;
mysql root@localhost:test> start transaction;
mysql root@localhost:test> select * from users;
+----+---------+---------------------+---------------------+------------+
| id | name    | created_at          | updated_at          | deleted_at |
+----+---------+---------------------+---------------------+------------+
| 1  | coolcao | 2018-05-28 12:48:43 | 2018-05-28 12:48:43 | <null>     |
| 2  | tom     | 2018-05-28 13:24:28 | 2018-05-28 13:24:28 | <null>     |
| 3  | lili    | 2018-05-31 08:54:28 | 2018-05-31 08:54:28 | <null>     |
+----+---------+---------------------+---------------------+------------+
mysql root@localhost:test> update users set name='coolcao222' where id=1;
mysql root@localhost:test> select * from users;
+----+------------+---------------------+---------------------+------------+
| id | name       | created_at          | updated_at          | deleted_at |
+----+------------+---------------------+---------------------+------------+
| 1  | coolcao222 | 2018-05-28 12:48:43 | 2018-05-28 12:48:43 | <null>     |
| 2  | tom        | 2018-05-28 13:24:28 | 2018-05-28 13:24:28 | <null>     |
| 3  | lili       | 2018-05-31 08:54:28 | 2018-05-31 08:54:28 | <null>     |
+----+------------+---------------------+---------------------+------------+

然后,在B终端,开启另外一个事务,进行数据查询:

mysql root@localhost:test> set session transaction isolation level read committed;
mysql root@localhost:test> start transaction;
mysql root@localhost:test> select * from users;
+----+---------+---------------------+---------------------+------------+
| id | name    | created_at          | updated_at          | deleted_at |
+----+---------+---------------------+---------------------+------------+
| 1  | coolcao | 2018-05-28 12:48:43 | 2018-05-28 12:48:43 | <null>     |
| 2  | tom     | 2018-05-28 13:24:28 | 2018-05-28 13:24:28 | <null>     |
| 3  | lili    | 2018-05-31 08:54:28 | 2018-05-31 08:54:28 | <null>     |
+----+---------+---------------------+---------------------+------------+

然后,将A事务提交:

commit;

这时,再在B查询 :

mysql root@localhost:test> select * from users;
+----+------------+---------------------+---------------------+------------+
| id | name       | created_at          | updated_at          | deleted_at |
+----+------------+---------------------+---------------------+------------+
| 1  | coolcao222 | 2018-05-28 12:48:43 | 2018-05-28 12:48:43 | <null>     |
| 2  | tom        | 2018-05-28 13:24:28 | 2018-05-28 13:24:28 | <null>     |
| 3  | lili       | 2018-05-31 08:54:28 | 2018-05-31 08:54:28 | <null>     |
+----+------------+---------------------+---------------------+------------+

从结果来看,也不难理解 read committed级别,对于一个事务,只能读取到当前事务的数据和其他已经提交的事务的数据,对于其他未提交事务的数据,读不到。

而且,从上面的实验结果中,我们也看到了,会话B在会话A提交事务前后查询的结果并不一致,这也就是上面所说的,不可重复读。

REPEATABLE READ 可重复读

我们将会话A设置为REPEATABLE READ :

mysql root@localhost:test> set session transaction isolation level repeatable read;
mysql root@localhost:test> start transaction;
mysql root@localhost:test> select * from users;
+----+---------+---------------------+---------------------+------------+
| id | name    | created_at          | updated_at          | deleted_at |
+----+---------+---------------------+---------------------+------------+
| 1  | coolcao | 2018-08-10 15:21:02 | 2018-08-10 15:21:02 | <null>     |
| 2  | tom     | 2018-08-10 15:21:07 | 2018-08-10 15:21:07 | <null>     |
| 3  | lili    | 2018-08-10 15:21:11 | 2018-08-10 15:21:11 | <null>     |
+----+---------+---------------------+---------------------+------------+

此时,我们在B终端插入一条数据:

mysql root@localhost:test> insert into users (id,name) values (4,'coco');
mysql root@localhost:test> commit;
mysql root@localhost:test> select * from users;
+----+---------+---------------------+---------------------+------------+
| id | name    | created_at          | updated_at          | deleted_at |
+----+---------+---------------------+---------------------+------------+
| 1  | coolcao | 2018-08-10 15:21:02 | 2018-08-10 15:21:02 | <null>     |
| 2  | tom     | 2018-08-10 15:21:07 | 2018-08-10 15:21:07 | <null>     |
| 3  | lili    | 2018-08-10 15:21:11 | 2018-08-10 15:21:11 | <null>     |
| 4  | coco    | 2018-08-10 15:23:50 | 2018-08-10 15:23:50 | <null>     |
+----+---------+---------------------+---------------------+------------+

在终端B中,插入一条记录,并提交,这时id=4的用户已经被插入到数据库。

此时,再回到终端A,查询:

mysql root@localhost:test> select * from users;
+----+---------+---------------------+---------------------+------------+
| id | name    | created_at          | updated_at          | deleted_at |
+----+---------+---------------------+---------------------+------------+
| 1  | coolcao | 2018-08-10 15:21:02 | 2018-08-10 15:21:02 | <null>     |
| 2  | tom     | 2018-08-10 15:21:07 | 2018-08-10 15:21:07 | <null>     |
| 3  | lili    | 2018-08-10 15:21:11 | 2018-08-10 15:21:11 | <null>     |
+----+---------+---------------------+---------------------+------------+

哎,查询的结果中,没有B刚插入的id=4的用户,这也就是说该级别的事务隔离,保证了在同一个事务中多次读取同样的记录的结果是一致的。这时,我们在A中插入一条记录:

mysql root@localhost:test> insert into users (id,name) values (4,'coco');
(1062, u"Duplicate entry '4' for key 'PRIMARY'")

哎,这个时候,数据库报错了,提示主键重复。明明我在这个事务中,查询的数据只有1,2,3,为什么插入4的时候提示主键冲突呢?是发生幻觉了么?是的,发生“幻读”了。由于REPEATABLE READ级别的隔离,在一个事务中,多次读取同样记录的结果是一致的,在这多次读取之间,被别的事务插入了新的数据,这时前事务再插入数据,必然会导致错误。

SERIALIZABLE 可串行化

我们将A,B同时设置为SERIALIZABLE, 然后在A开启是个事务,做一个简单查询:

mysql root@localhost:test> set session transaction isolation level serializable;
mysql root@localhost:test> start transaction;
mysql root@localhost:test> select * from users where id<10;
+----+---------+---------------------+---------------------+------------+
| id | name    | created_at          | updated_at          | deleted_at |
+----+---------+---------------------+---------------------+------------+
| 1  | coolcao | 2018-08-10 15:21:02 | 2018-08-10 15:21:02 | <null>     |
| 2  | tom     | 2018-08-10 15:21:07 | 2018-08-10 15:21:07 | <null>     |
| 3  | lili    | 2018-08-10 15:21:11 | 2018-08-10 15:21:11 | <null>     |
| 4  | coco    | 2018-08-10 15:23:50 | 2018-08-10 15:23:50 | <null>     |
| 5  | juli    | 2018-08-10 15:31:32 | 2018-08-10 15:31:32 | <null>     |
+----+---------+---------------------+---------------------+------------+

此时,A事务并未提交,然后在B再开启一个事务,进行插入操作:

mysql root@localhost:test> set session transaction isolation level serializable;
mysql root@localhost:test> start transaction;
mysql root@localhost:test> insert into users (id,name) values (6,'kate');
(1205, u'Lock wait timeout exceeded; try restarting transaction')

你会发现,哎我去,B事务被挂住了,然后过了一段时间,提示了错误 (1205, u'Lock wait timeout exceeded; try restarting transaction') ,说等待锁超时。

是的,在串行化级别,会在读取的每一行数据都加上锁,也就是说,上面A事务在读取时,已经加了锁,此时B事务在插入操作时,得等待锁的放开,时间一长,A锁未放开,B就报错了。

从实验中可以看出,可串行化级别,由于要保证避免幻读而加了锁导致效率以及可能会触发的等待锁超时等错误,实际应用中,该级别的事务隔离也很少使用。

对照着实验结果,来理解上面四个隔离级别,就容易理解了。

1、具有1-5工作经验的,面对目前流行的技术不知从何下手,需要突破技术瓶颈的可以加群。

2、在公司待久了,过得很安逸,但跳槽时面试碰壁。需要在短时间内进修、跳槽拿高薪的可以加群。

3、如果没有工作经验,但基础非常扎实,对java工作机制,常用设计思想,常用java开发框架掌握熟练的,可以加群。

4、觉得自己很牛B,一般需求都能搞定。但是所学的知识点没有系统化,很难在技术领域继续突破的可以加群。

5.群号 468947140,点击链接加入群聊【Java-BATJ企业级资深架构】:https://jq.qq.com/?_wv=1027&k=57VIGuh

6.阿里Java高级大牛直播讲解知识点,分享知识,知识点都是各位老师多年工作经验的梳理和总结,带着大家全面、科学地建立自己的技术体系和技术认知!

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
2月前
|
存储 SQL 关系型数据库
MySQL的事务隔离级别
【10月更文挑战第17天】MySQL的事务隔离级别
116 43
|
2月前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1673 14
|
3月前
|
存储 Oracle 关系型数据库
Oracle和MySQL有哪些区别?从基本特性、技术选型、字段类型、事务、语句等角度详细对比Oracle和MySQL
从基本特性、技术选型、字段类型、事务提交方式、SQL语句、分页方法等方面对比Oracle和MySQL的区别。
618 18
Oracle和MySQL有哪些区别?从基本特性、技术选型、字段类型、事务、语句等角度详细对比Oracle和MySQL
|
2月前
|
SQL 关系型数据库 MySQL
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?
尼恩,一位40岁的资深架构师,通过其丰富的经验和深厚的技術功底,为众多读者提供了宝贵的面试指导和技术分享。在他的读者交流群中,许多小伙伴获得了来自一线互联网企业的面试机会,并成功应对了诸如事务ACID特性实现、MVCC等相关面试题。尼恩特别整理了这些常见面试题的系统化解答,形成了《MVCC 学习圣经:一次穿透MYSQL MVCC》PDF文档,旨在帮助大家在面试中展示出扎实的技术功底,提高面试成功率。此外,他还编写了《尼恩Java面试宝典》等资料,涵盖了大量面试题和答案,帮助读者全面提升技术面试的表现。这些资料不仅内容详实,而且持续更新,是求职者备战技术面试的宝贵资源。
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?
|
2月前
|
存储 关系型数据库 MySQL
RR隔离级别在MySQL中的实现与幻读问题探讨
【10月更文挑战第3天】在数据库管理系统中,事务隔离级别是确保数据一致性和并发性能的关键要素。MySQL作为广泛使用的关系型数据库管理系统,支持多种事务隔离级别,其中可重复读(Repeatable Read,简称RR)是其默认隔离级别。本文将深入探讨RR隔离级别在MySQL中的实现原理,以及RR隔离级别下幻读问题的产生与解决方案。
101 2
|
3月前
|
SQL 关系型数据库 MySQL
MySQL基础:事务
本文详细介绍了数据库事务的概念及操作,包括事务的定义、开启、提交与回滚。事务作为一组不可分割的操作集合,确保了数据的一致性和完整性。文章还探讨了事务的四大特性(原子性、一致性、隔离性、持久性),并分析了并发事务可能引发的问题及其解决方案,如脏读、不可重复读和幻读。最后,详细讲解了不同事务隔离级别的特点和应用场景。
158 4
MySQL基础:事务
|
3月前
|
关系型数据库 MySQL 数据库
深入理解MySQL数据库隔离级别
深入理解MySQL数据库隔离级别
133 1
|
4月前
|
人工智能 小程序 关系型数据库
【MySQL】黑悟空都掌握的技能,数据库隔离级别全攻略
本文以热门游戏《黑神话:悟空》为契机,深入浅出地解析了数据库事务的四种隔离级别:读未提交、读已提交、可重复读和串行化。通过具体示例,展示了不同隔离级别下的事务行为差异及可能遇到的问题,如脏读、不可重复读和幻读等。此外,还介绍了在MySQL中设置隔离级别的方法,包括全局和会话级别的调整,并通过实操演示了各隔离级别下的具体效果。本文旨在帮助开发者更好地理解和运用事务隔离级别,以提升数据库应用的一致性和性能。
145 2
【MySQL】黑悟空都掌握的技能,数据库隔离级别全攻略
|
2月前
|
SQL 关系型数据库 MySQL
【MySQL】索引和事务
【MySQL】索引和事务
56 0
|
3月前
|
SQL Oracle 关系型数据库
详解 MySQL 的事务以及隔离级别
详解 MySQL 的事务以及隔离级别
46 0