談談數據庫隔離級別


1.隔離級別介紹

  隔離級別並不是某個SQL數據庫所特有的,而所有SQL數據庫都要實現的一種並發事務隔離機制。隔離性其實比想象的要復雜。在SQL標准中定義了四種隔離級別,每一種隔離級別都規定了一個事務中所作的修改,哪些在事務內和事務間是可見的,哪些是不可見的。較低的級別的隔離通常可以執行更高的並發,系統的開銷也更低,然而數據的改變在事務間幾乎是透明,也更容易引發各種無法預估的問題。下面簡單介紹下四種隔離級別:

Read Uncommitted(未提交讀)

  在Read Uncommitted級別,事務的修改,即使沒有提交,對其他事務也是可見的。事務可以讀取其它事務未提交的數據,這也被稱為臟讀(Dirty Read)。這個級別會導致很多問題,從性能上說,Read Uncommitted不會比其他級別好太多,但缺乏其他級別的很多好處。除非真的有非常必要的理由,在實際應用中一般很少使用。

Read Committed(已提交讀)

  大多數數據庫的默認隔離級別都是Read Committed(但MySQL 不是)。Read Committed滿足隔離性的簡單定義:一個事務開始時,只能看見已經提交的事務所作的修改。換句話說,一個事務從開始知道提交之前,所作的任何修改對其它事務是不可見的。這個級別有時候也叫做不可重復讀(Nonrepeatable Read),因為事務可以讀到另一個事務提交的數據,可能存在先后兩次讀取到的數據不一致(中間存在另一個事務提交的數據),執行兩次同樣的查詢,得到的結果不一致。

Repeatable Read(可重復讀,MySQL默認隔離級別)

  Repeatable Read解決了不可重復讀的問題。該級別保證了在同一個事務中多次讀取同樣的記錄的結果是一致的。但是理論上,可重復讀隔離級別還是無法解決另外一個幻讀問題。所謂幻讀,指的是當某個事務在讀取某個范圍內的記錄時,另一個事務又在這個范圍內插入了新記錄,當之前的事務再次讀取該范圍的記錄,會產生幻行。也就是說可重復讀只會在修改事務有效,比如一個事務先后讀取同一個范圍的記錄,而在這中間另一個事務對某一條記錄做了修改,當前事務兩次讀取到的結果是一樣的,但是如果是新增數據就會產生幻讀的現象。為了解決在可重復讀級別下發生的幻讀的問題,MySQL的InnoDB和XtraDB存儲引擎通過多版本並發控制機制(MVCC)解決了幻讀的問題。

Serializable(可串化讀)

  Serializable是最高的隔離級別。它通過強制事務串行執行,避免幻讀問題。簡單來說Serializable會在讀取的每一行上加鎖,所以可能導致大量的超時和鎖競爭問題。實際開發中也很少用到這個隔離級別,只有在非常需要保證數據的一致性而且可以接受沒有並發的情況下,才考慮采用該級別。

2.隔離級別演示

  以mysql演示前三個隔離級別(串讀個人覺得沒啥必要)。以表user(id,name,salary)為例。

Read Uncommitted(未提交讀)演示

 設置隔離級別為Read Uncommitted,並開啟兩個事務A,B。事務A先開啟事務並查詢user,數據為空。

mysql> set session transaction isolation level read uncommitted; --設置隔離級別
Query OK, 0 rows affected

mysql> start transaction; --事務A
Query OK, 0 rows affected

mysql> select * from user;--查詢user表,數據為空
Empty set  

再開啟事務B,新增一條記錄不提交。

mysql> set session transaction isolation level read uncommitted; --設置隔離級別
Query OK, 0 rows affected

mysql> start transaction; --事務B
Query OK, 0 rows affected

mysql> insert into user(name,salary) values ('jack',1000); --插入一條數據未提交
Query OK, 1 row affected
mysql> 

 這時事務A再查user表能查到數據,查到B未提交的數據

mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected

mysql> start transaction;
Query OK, 0 rows affected

mysql> select * from user;
Empty set

mysql> select * from user; --事務A再查詢,能查到數據
+----+------+--------+
| id | name | salary |
+----+------+--------+
|  1 | jack | 1000   |
+----+------+--------+
1 row in set

Read Committed(已提交讀)演示 

設置隔離級別為Read Committed,並先后開啟三個事務A,B,C。事務A先開啟查詢user表,查詢到一條數據。

mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.01 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.01 sec)

mysql> select * from user;
+----+------+--------+
| id | name | salary |
+----+------+--------+
|  1 | jack |   1000 |
+----+------+--------+
1 row in set (0.02 sec)

mysql> 

再開啟B事務,新增一條數據不提交。

mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.01 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.01 sec)

mysql> insert into user(name,salary) values ('lucy',2000);
Query OK, 1 row affected (0.01 sec)
mysql> 

再回到A事務,查詢user表,還是一條數據

mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.01 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.01 sec)

mysql> select * from user;
+----+------+--------+
| id | name | salary |
+----+------+--------+
|  1 | jack |   1000 |
+----+------+--------+
1 row in set (0.02 sec)

mysql> select * from user;
+----+------+--------+
| id | name | salary |
+----+------+--------+
|  1 | jack |   1000 |
+----+------+--------+
1 row in set (0.03 sec)

mysql> 

回到B事務,提交剛才插入的一條數據。

mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.01 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.01 sec)

mysql> insert into user(name,salary) values ('lucy',2000);
Query OK, 1 row affected (0.01 sec)
mysql> commit;
Query OK, 0 rows affected (0.02 sec)

mysql> 

最后回到A事務,再次查詢user表,能查到B事務提交的數據

mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.01 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.01 sec)

mysql> select * from user;
+----+------+--------+
| id | name | salary |
+----+------+--------+
|  1 | jack |   1000 |
+----+------+--------+
1 row in set (0.02 sec)

mysql> select * from user;
+----+------+--------+
| id | name | salary |
+----+------+--------+
|  1 | jack |   1000 |
+----+------+--------+
1 row in set (0.03 sec)

mysql> select * from user;
+----+------+--------+
| id | name | salary |
+----+------+--------+
|  1 | jack |   1000 |
|  2 | lucy |   2000 |
+----+------+--------+
2 rows in set (0.03 sec)

mysql> 

再開啟一個事務C,事務C修改一條記錄並提交。

mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.01 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.01 sec)

mysql> update user set salary=salary-200 where id=1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> commit;
Query OK, 0 rows affected (0.01 sec)

mysql> 

然后繼續回到A事務中,讀取user表數據,可以讀到C事務已經修改的數據

mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.01 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.01 sec)

mysql> select * from user;
+----+------+--------+
| id | name | salary |
+----+------+--------+
|  1 | jack |   1000 |
+----+------+--------+
1 row in set (0.02 sec)

mysql> select * from user;
+----+------+--------+
| id | name | salary |
+----+------+--------+
|  1 | jack |   1000 |
+----+------+--------+
1 row in set (0.03 sec)

mysql> select * from user;
+----+------+--------+
| id | name | salary |
+----+------+--------+
|  1 | jack |   1000 |
|  2 | lucy |   2000 |
+----+------+--------+
2 rows in set (0.03 sec)

mysql> select * from user;
+----+------+--------+
| id | name | salary |
+----+------+--------+
|  1 | jack |    800 |
|  2 | lucy |   2000 |
+----+------+--------+
2 rows in set (0.18 sec)

mysql> 

所以說提交讀,會讀取到已經提交的數據,不管數據是修改還是新增,只要提交了,在另一個事務中前后讀取的結果會不一致。 

Repeatable Read(可重復讀)演示

 設置事務隔離級別為Repeatable Read。先后開啟事務A,B。首先開啟事務A,查詢user表數據,可以查到2條數據。

mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.01 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.01 sec)

mysql> select * from user;
+----+------+--------+
| id | name | salary |
+----+------+--------+
|  1 | jack |    800 |
|  2 | lucy |   2000 |
+----+------+--------+
2 rows in set (0.03 sec)

mysql> 

再開啟B事務,修改一條數據並提交。

mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.01 sec)

mysql>  start transaction;
Query OK, 0 rows affected (0.01 sec)

mysql> update user set salary=600 where id=1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> commit;
Query OK, 0 rows affected (0.05 sec)

mysql> 

回到A事務繼續讀取user表數據,發現還是之前的數據,並沒有因為B事務的修改發生改變,重復讀取的結果一樣。

mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.01 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.01 sec)

mysql> select * from user;
+----+------+--------+
| id | name | salary |
+----+------+--------+
|  1 | jack |    800 |
|  2 | lucy |   2000 |
+----+------+--------+
2 rows in set (0.03 sec)

mysql> select * from user;
+----+------+--------+
| id | name | salary |
+----+------+--------+
|  1 | jack |    800 |
|  2 | lucy |   2000 |
+----+------+--------+
2 rows in set (0.03 sec)

mysql> 

這個時候開啟事務C,往user表里插入一條數據並提交。

mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.01 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.01 sec)

mysql> insert into user(name , salary) values ('lucy',3000);
Query OK, 1 row affected (0.04 sec)
mysql> commit;
Query OK, 0 rows affected (0.03 sec)

mysql> 

然后再次回到事務A,查詢user表的數據,發現仍然還是2條。

mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.01 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.01 sec)

mysql> select * from user;
+----+------+--------+
| id | name | salary |
+----+------+--------+
|  1 | jack |    800 |
|  2 | lucy |   2000 |
+----+------+--------+
2 rows in set (0.03 sec)

mysql> select * from user;
+----+------+--------+
| id | name | salary |
+----+------+--------+
|  1 | jack |    800 |
|  2 | lucy |   2000 |
+----+------+--------+
2 rows in set (0.03 sec)

mysql> select * from user;
+----+------+--------+
| id | name | salary |
+----+------+--------+
|  1 | jack |    800 |
|  2 | lucy |   2000 |
+----+------+--------+
2 rows in set (0.03 sec)

mysql> 

那么問題來了,前面我們提到過,可重復讀是會產生幻讀的情況的。可重復讀只能保證在范圍內的數據被其它事務修改並提交,在當前事務讀取到的數據一致,但是如果某一個事務在范圍內新增了數據,當前事務是能查到的。也就是說事務A三次查詢user表的所有數據,第一次和第二次之間,事務B修改了一條數據,在可重復讀級別下,事務A讀到的數據是一樣的。但是C事務是在范圍內(select *)新增了一條數據,按照幻讀的現象,事務A應該會讀取到這條記錄才對。其實,這里就是采用的MVCC(多版本並發控制)。MVCC機制使得一個事務只能訪問到在事務開啟之前所有已提交的事務產生的數據。關於該機制的原理后續再談。

 

注意:本文僅代表個人理解和看法喲!和本人所在公司和團體無任何關系!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM