MySQL中事务隔离深入理解

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


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,如需转载请自行联系原作者
上一篇:如何在Eclipse下安装SVN插件——subclipse以及安装svn常出现的错误总结


下一篇:jsp页面引入css文件报错 javax.servlet cannot be resolved to a type