MySQL中事务隔离深入理解

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介:

本人也曾经对事务四个隔离级别非常困惑,网上搜索很多文章后也是不是特别深刻理解。不过现在不会了,你看完本篇博文后也会如此。


1、MySQL的隔离级别存在的意义

首先大家要理解MySQL的四个会话隔离级别的用途。MySQL是允许多用户连接同时操作数据的,为了避免彼此之间更新数据时的干扰,设定了会话隔离级别实现它们是否能实时看到其他会话更新的数据。


2、接下来我们深入理解一下MySQL的四个隔离级别代表的含义并做演示。如果能理解四个隔离级别,那么脏读、不可重复读、可重复读、幻读就非常容易理解了。


(1)READ-UNCOMMITTED(读未提交):两个会话都开始事务以后,一个会话修改了数据,另一个会话查询立即更新;

演示:READ-UNCOMMITTED对会话隔离的影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
1、先简单创建一个数据和表并插入数据
 
MariaDB [(none)]>  create  database  test;
 
MariaDB [(none)]> use test;
Database  changed
MariaDB [test]>  create  table  t1 (id  int );
 
MariaDB [test]>  desc  t1;
+ -------+---------+------+-----+---------+-------+
| Field | Type    |  Null  Key  Default  | Extra |
+ -------+---------+------+-----+---------+-------+
| id    |  int (11) | YES  |     |  NULL     |       |
+ -------+---------+------+-----+---------+-------+
1 row  in  set  (0.01 sec)
 
确保表格是InnoDB存储引擎,InnoDB才支持事务
MariaDB [test]> show  table  status\G
*************************** 1. row ***************************
            Name : t1
          Engine: InnoDB
 
          
MariaDB [test]>  insert  into  t1 (id)  values  (1),(2);
 
MariaDB [test]>  select  from  t1;
+ ------+
| id |
+ ------+
| 1  |
| 2  |
+ ------+
 
 
2、建立两个MySQL会话连接,禁用MySQL的事务自动提交功能和修改会话隔离级别为 READ - UNCOMMITTED
 
MariaDB [test]>  set  session autocommit=0;
MariaDB [test]> show session variables  like  'autocommit' ;
+ ---------------+-------+
| Variable_name | Value |
+ ---------------+-------+
| autocommit   |  OFF   |
+ ---------------+-------+
 
MariaDB [test]>  set  session tx_isolation= 'read-uncommitted' ;
MariaDB [test]> show session variables  like  'tx_isolation' ;
+ ---------------+------------------+
| Variable_name | Value     |
+ ---------------+------------------+
| tx_isolation  |  READ - UNCOMMITTED  |
+ ---------------+------------------+
 
3、会话1启动事务,然后更新数据
MariaDB [test]> start  transaction ;
MariaDB [test]>  select  from  t1;
+ ------+
| id   |
+ ------+
|    1 |
|    2 |
+ ------+
MariaDB [test]>  update  t1  set  id=22  where  id=2;
 
4、会话2启动事务,并且可以立刻读取到更新后数据
MariaDB [test]> start  transaction ;
MariaDB [test]>  select  from  t1;
+ ------+
| id   |
+ ------+
|    1 |
|   22 |
+ ------+
 
5、会话1再次更新数据
MariaDB [test]>  update  t1  set  id=222  where  id=22;
 
6、会话2再次立刻读取更新后的数据
MariaDB [test]>  select  from  t1;
+ ------+
| id   |
+ ------+
|    1 |
|  222 |
+ ------+
 
7、会话1和会话2执行提交结束会话,会话1做出的修改永久生效。
commit ;
 
8、以上步骤总结归纳:
会话1        会话2
开始事务       开始事务
修改1        读取立刻更新
修改2        读取立刻更新
提交         提交

总结:两个会话之间隔离级别是READ-UNCOMMITTED的话,会话1更新了数据,会话2立刻会看到数据。可能有些读者会认为这样子实时更新是最好的。其实不然,因为如果会话1回滚了,会话2读取到的数据其实没有任何意义,这些数据因为不是最终结果数据,称为“脏数据”所以通常情况下,会话1在提交之前的脏数据不应该被其他会话看见的,所以隔离级别应该加强一些,也就是,会话1的未提交前的数据修改不应该被其他用户看见的。


(2)READ-COMMITTED(读提交):两个会话都开始事务以后,一个会话修改了数据提交后,另一个会话的查询才会更新;

演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
1、会话1和会话2修改会话隔离级别为 READ - COMMITTED
MariaDB [test]>  set  session tx_isolation= 'read-committed' ;
 
2、当前数据状态
MariaDB [test]>  select  from  t1;
+ ------+
| id   |
+ ------+
|    1 |
|  222 |
+ ------+
 
3、会话1开始事务并执行修改操作
MariaDB [test]> start  transaction ;
MariaDB [test]>  update  t1  set  id=2  where  id=222;
MariaDB [test]>  select  from  t1;
+ ------+
| id   |
+ ------+
|    1 |
|    2 |
+ ------+
 
4、会话2开始事务并读取t1表
MariaDB [test]> start  transaction ;
MariaDB [test]>  select  from  t1;
+ ------+
| id   |
+ ------+
|    1 |
|  222 |
+ ------+
说明:此时,由于会话1还没提交数据,所以t1表读取到的还是原值222
 
5、会话1提交事务
MariaDB [test]>  commit ;
 
6、会话2读取t1表
MariaDB [test]>  select  from  t1;
+ ------+
| id   |
+ ------+
|    1 |
|    2 |
+ ------+
说明:此时,由于会话1已提交数据,所以t1表读取到会话1更新后的值2
 
7、会话2提交结束事务
MariaDB [test]>  commit ;
 
8、以上步骤总结过程
会话1      会话2
开始事务     开始事务
更新数据1    不能读取到更新值
提交       可以读取到更新值1
          提交

总结:两个会话之间隔离级别是READ-COMMITTED的话,会话1更新了数据不会实时同步到会话2上,也就是说会话2查询不到会话1更新的数据。必须要等会话1提交以后,会话2才能查询到,这就是读提交的含义所在。但是上述步骤我们延伸一下

会话1             会话2

开始事务        开始事务

更新数据1      不能读取到更新值

提交               可以读取到更新值1

开始事务

更新数据2      可以读取到更新值1,不能读取到更新值2

提交               可以读取到更新值2

                      提交

在这个示例中,我们看到会话2在一次会话过程中读取同一个数据却得到两个不一样数值,此谓不可重复读。如果要想保证在会话在一次事务过程中一个数据只能读取到一个值,就需要加强隔离级别了。就是即将讲述的可重复读。



(3)REPEATABLE-READ(可重读):两个会话都开始事务以后,一个会话修改了数据,另一个会话必须要等到两个会话都提交后查询才会更新。所以,可重读就是要保证,会话的一次事务过程读取一个数值只能获取到一个值而不是多个值。

演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
1、会话1的隔离级别修改为 repeatable - read 并开始事务修改数据
 
MariaDB [test]>  set  tx_isolation= "repeatable-read" ;
MariaDB [test]> show variables  like  'tx_isolation' ;
+ ---------------+-----------------+
| Variable_name | Value           |
+ ---------------+-----------------+
| tx_isolation  |  REPEATABLE - READ  |
+ ---------------+-----------------+
 
MariaDB [test]> start  transaction ;
 
MariaDB [test]>  select  from  t1;
+ ------+
| id   |
+ ------+
|   1 |
|  222 |
+ ------+
 
MariaDB [test]>  update  t1  set  id=2  where  id=222;
 
MariaDB [test]>  select  from  t1;
+ ------+
| id   |
+ ------+
|    1 |
|    2 |
+ ------+
 
2、会话2修改隔离级别并查看数据
MariaDB [test]>  set  tx_isolation= "repeatable-read" ;
MariaDB [test]> show variables  like  'tx_isolation' ;
+ ---------------+-----------------+
| Variable_name | Value           |
+ ---------------+-----------------+
| tx_isolation  |  REPEATABLE - READ  |
+ ---------------+-----------------+
 
MariaDB [test]> start  transaction ;
 
MariaDB [test]>  select  from  t1;
+ ------+
| id   |
+ ------+
|   1 |
|  222 |
+ ------+
说明:此时会话2只能看到原值222而不能看到新值222
 
3、会话1提交事务
commit ;
 
4、会话2查询数据依然看不到更新后的值
MariaDB [test]>  select  from  t1;
+ ------+
| id   |
+ ------+
|    1 |
|  222 |
+ ------+
rows  in  set  (0.00 sec)
 
5、会话2提交本次事务后才能看到更新后的值
MariaDB [test]>  commit ;
MariaDB [test]>  select  from  t1;
+ ------+
| id   |
+ ------+
|    1 |
|    2 |
+ ------+
 
6、以上步骤总结:
会话1        会话2
开始事务     开始事务
修改数值1    不能看到修改数值1,只能看到原值
提交       不能看到修改数值1,只能看到原值
          提交
          可以看到修改数值1

总结:两个会话之间隔离级别是REPEATABLE-READ的话,会话1更新了数据不会实时同步到会话2上,也就是说会话2查询不到会话1更新的数据,必须要等会话1和会话2都提交以后,会话2才能查询到,这就保证了会话2在一次事务中读取一个数据只能得到一个值——即“可重复读”的含义。虽然REPEATABLE-READ保证了可重复读,但是读到的数据其实不准确的,因为会话1提交事务后新数值已经写入硬盘了,但会话1还是能读取到旧的数值,此谓幻读——读到的数据并不是真实数据,只是幻影罢了。所以在会话1提交之后,会话2就不应该还能读取到“幻影数据”了,如何实现?就是SERIALIZABILE这种隔离级别。



(4)SERIALIZABILE(串行化):我们先不讨论串行化是什么含义,我们来看看SERIALIZABILE是否真的能够解决幻读问题。

演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
1、会话1和会话2修改隔离级别为SERIALIZABILE,并启动事务修改数据
MariaDB [test]>  set  session tx_isolation= 'serializable' ;
MariaDB [test]> start  transaction ;
 
2、会话1修改数据并提交
MariaDB [test]>  select  from  t1;
+ ------+
| id |
+ ------+
|  1 |
| 22 |
+ ------+
MariaDB [test]>  update  t1  set  id=222  where  id=22;
Query OK, 1 row affected (0.00 sec)
Rows  matched: 1  Changed: 1  Warnings: 0
MariaDB [test]>  commit ;
 
2、会话2此时查询数据,会怎么样呢?还会幻读吗?
MariaDB [test]>  select  from  t1;
+ ------+
| id   |
+ ------+
|    1 |
|    2 |
+ ------+
说明:会话2可以查询到会话1更新后的数据,不是幻读。





虽然serializable可以解决幻读问题,但是串行化的却引入了新问题——并发能力不足。串行化的含义是指一个回话的读写和另一个会话的读写串行执行而不能并行执行。因为串行化隔离模型中,会话在读/写数据时会对操作的行加锁——读取数据时加读锁,修改数据时加写锁。所以,串行化隔离模型需要分加锁读和加锁写来讨论,具体如下:

加锁读:两个会话都开始事务以后,如果会话1发起查询会对查询的涉及的行施加读锁,读锁不会阻塞其他会话查询数据但会阻塞更新数据。被阻塞的更新会等待超时或在会话1结束(提交/回滚)后继续操作。

演示:

(待更新)


加锁写:两个会话都开始事务以后,如果会话1更新数据会对更新的涉及的行施加写锁,写锁会阻塞其他会话对加锁行再次施加写锁或读锁,也就是说,其他会话不能读取或更新该行数据。被阻塞的更新/读取会在第一个会话结束后继续进行或者等待超时。

演示:

(待更新)








本文转自 zhuhc1988 51CTO博客,原文链接:http://blog.51cto.com/changeflyhigh/1881614,如需转载请自行联系原作者
相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
12天前
|
SQL 关系型数据库 MySQL
轻松入门MySQL:保障数据完整性,MySQL事务在进销存管理系统中的应用(12)
轻松入门MySQL:保障数据完整性,MySQL事务在进销存管理系统中的应用(12)
|
1月前
|
关系型数据库 MySQL 数据库
MySQL事务(简单明了)
MySQL事务(简单明了)
|
1月前
|
SQL 关系型数据库 MySQL
MySQL索引与事务
MySQL索引与事务
|
2月前
|
存储 关系型数据库 MySQL
Mysql高可用|索引|事务 | 调优
Mysql高可用|索引|事务 | 调优
|
1月前
|
关系型数据库 MySQL 数据库
深入探讨MySQL并发事务的问题及解决方案
深入探讨MySQL并发事务的问题及解决方案
54 0
|
2月前
|
NoSQL 关系型数据库 MySQL
MySQL - 深入理解 MySQL 的事务和隔离级别
MySQL - 深入理解 MySQL 的事务和隔离级别
142 0
|
1月前
|
SQL 关系型数据库 MySQL
【MySQL 数据库】4、MySQL 事务学习
【MySQL 数据库】4、MySQL 事务学习
44 0
|
2月前
|
SQL 关系型数据库 MySQL
Mysql事务隔离级别和锁特性
Mysql事务隔离级别和锁特性
|
5天前
|
存储 SQL 关系型数据库
【MySQL实战笔记】03.事务隔离:为什么你改了我还看不见?-02
【4月更文挑战第7天】数据库通过视图实现事务隔离,不同隔离级别如读未提交、读已提交、可重复读和串行化采用不同策略。以可重复读为例,MySQL使用多版本并发控制(MVCC),每个事务有其独立的视图。回滚日志在无更早视图时被删除。长事务可能导致大量存储占用,应避免。事务启动可显式用`begin`或设置`autocommit=0`,但后者可能意外开启长事务。建议使用`autocommit=1`并显式管理事务,若需减少交互,可使用`commit work and chain`。
25 5
|
19天前
|
存储 缓存 关系型数据库
MySQL事务的四大特性是如何保证的
在MySQL数据库中还有一种二进制日志,其用来基于时间点的还原及主从复制。从表面上来看其和重做日志非常相似,都是记录了对于数据库操作的日志。但是,从本质上来看有着非常大的不同。
10 1