【MySQL】MySQL的四種事務隔離級別

lhrbest發表於2017-07-09

【MySQL】MySQL的四種事務隔離級別



本文實驗的測試環境:Windows 10+cmd+MySQL5.6.36+InnoDB

一、事務的基本要素(ACID)

  1、原子性(Atomicity):事務開始後所有操作,要麼全部做完,要麼全部不做,不可能停滯在中間環節。事務執行過程中出錯,會回滾到事務開始前的狀態,所有的操作就像沒有發生一樣。也就是說事務是一個不可分割的整體,就像化學中學過的原子,是物質構成的基本單位。

   2、一致性(Consistency):事務開始前和結束後,資料庫的完整性約束沒有被破壞 。比如A向B轉賬,不可能A扣了錢,B卻沒收到。

   3、隔離性(Isolation):同一時間,只允許一個事務請求同一資料,不同的事務之間彼此沒有任何干擾。比如A正在從一張銀行卡中取錢,在A取錢的過程結束前,B不能向這張卡轉賬。

   4、永續性(Durability):事務完成後,事務對資料庫的所有更新將被儲存到資料庫,不能回滾。

  小結:原子性是事務隔離的基礎,隔離性和永續性是手段,最終目的是為了保持資料的一致性。

 

二、事務的併發問題

  1、髒讀:事務A讀取了事務B更新的資料,然後B回滾操作,那麼A讀取到的資料是髒資料

  2、不可重複讀:事務 A 多次讀取同一資料,事務 B 在事務A多次讀取的過程中,對資料作了更新並提交,導致事務A多次讀取同一資料時,結果 不一致。

  3、幻讀:系統管理員A將資料庫中所有學生的成績從具體分數改為ABCDE等級,但是系統管理員B就在這個時候插入了一條具體分數的記錄,當系統管理員A改結束後發現還有一條記錄沒有改過來,就好像發生了幻覺一樣,這就叫幻讀。

  小結:不可重複讀的和幻讀很容易混淆,不可重複讀側重於修改,幻讀側重於新增或刪除。解決不可重複讀的問題只需鎖住滿足條件的行,解決幻讀需要鎖表

 

三、MySQL事務隔離級別

事務隔離級別 髒讀 不可重複讀 幻讀
讀未提交(read-uncommitted)
不可重複讀(read-committed)
可重複讀(repeatable-read)
序列化(serializable)

 

 

 

 

 

mysql預設的事務隔離級別為repeatable-read

 

四、用例子說明各個隔離級別的情況

  1、讀未提交:

    (1)開啟一個客戶端A,並設定當前事務模式為read uncommitted(未提交讀),查詢表account的初始值:

 

    (2)在客戶端A的事務提交之前,開啟另一個客戶端B,更新表account:

 

 

    (3)這時,雖然客戶端B的事務還沒提交,但是客戶端A就可以查詢到B已經更新的資料:

 

    (4)一旦客戶端B的事務因為某種原因回滾,所有的操作都將會被撤銷,那客戶端A查詢到的資料其實就是髒資料:

 

     (5)在客戶端A執行更新語句update account set balance = balance - 50 where id =1,lilei的balance沒有變成350,居然是400,是不是很奇怪,資料的一致性沒問啊,如果你這麼想就太天真 了,在應用程式中,我們會用400-50=350,並不知道其他會話回滾了,要想解決這個問題可以採用讀已提交的隔離級別

 

  2、讀已提交

    (1)開啟一個客戶端A,並設定當前事務模式為read committed(未提交讀),查詢表account的初始值:

 

    (2)在客戶端A的事務提交之前,開啟另一個客戶端B,更新表account:

 

    (3)這時,客戶端B的事務還沒提交,客戶端A不能查詢到B已經更新的資料,解決了髒讀問題:

 

    (4)客戶端B的事務提交

    (5)客戶端A執行與上一步相同的查詢,結果 與上一步不一致,即產生了不可重複讀的問題,在應用程式中,假設我們處於客戶端A的會話,查詢到lilei的balance為450,但是其他事務將lilei的balance值改為400,我們並不知道,如果用450這個值去做其他操作,是有問題的,不過這個機率真的很小哦,要想避免這個問題,可以採用可重複讀的隔離級別

 

   3、可重複讀

     (1)開啟一個客戶端A,並設定當前事務模式為repeatable read,查詢表account的初始值:

    (2)在客戶端A的事務提交之前,開啟另一個客戶端B,更新表account並提交,客戶端B的事務居然可以修改客戶端A事務查詢到的行,也就是mysql的可重複讀不會鎖住事務查詢到的行,這一點出乎我的意料,sql標準中事務隔離級別為可重複讀時,讀寫操作要鎖行的,mysql居然沒有鎖,我了個去。在應用程式中要注意給行加鎖,不然你會以步驟(1)中lilei的balance為400作為中間值去做其他操作

    (3)在客戶端A執行步驟(1)的查詢:

    (4)執行步驟(1),lilei的balance仍然是400與步驟(1)查詢結果一致,沒有出現不可重複讀的 問題;接著執行update balance = balance - 50 where id = 1,balance沒有變成400-50=350,lilei的balance值用的是步驟(2)中的350來算的,所以是300,資料的一致性倒是沒有被破壞,這個有點神奇,也許是mysql的特色吧

mysql> select * from account;
+------+--------+---------+
| id   | name   | balance |
+------+--------+---------+
|    1 | lilei  |     400 |
|    2 | hanmei |   16000 |
|    3 | lucy   |    2400 |
+------+--------+---------+
3 rows in set (0.00 sec)


mysql> update account set balance = balance - 50 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0


mysql> select * from account;
+------+--------+---------+
| id   | name   | balance |
+------+--------+---------+
|    1 | lilei  |     300 |
|    2 | hanmei |   16000 |
|    3 | lucy   |    2400 |
+------+--------+---------+
3 rows in set (0.00 sec) 
 
 

    (5) 在客戶端A開啟事務,查詢表account的初始值

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


mysql> select * from account;
+------+--------+---------+
| id | name | balance |
+------+--------+---------+
| 1 | lilei | 300 |
| 2 | hanmei | 16000 |
| 3 | lucy | 2400 |
+------+--------+---------+
3 rows in set (0.00 sec) 



	

    (6)在客戶端B開啟事務,新增一條資料,其中balance欄位值為600,並提交

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

mysql> insert into account values(4,'lily',600);
Query OK, 1 row affected (0.00 sec)

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

    (7) 在客戶端A計算balance之和,值為300+16000+2400=18700,沒有把客戶端B的值算進去,客戶端A提交後再計算balance之和,居然變成了19300,這是因為把客戶端B的600算進去了

,站在客戶的角度,客戶是看不到客戶端B的,它會覺得是天下掉餡餅了,多了600塊,這就是幻讀,站在開發者的角度,資料的 一致性並沒有破壞。但是在應用程式中,我們得程式碼可能會把18700提交給使用者了,如果你一定要避免這情況小機率狀況的發生,那麼就要採取下面要介紹的事務隔離級別“序列化”

mysql> select sum(balance) from account;
+--------------+
| sum(balance) |
+--------------+
| 18700 |
+--------------+
1 row in set (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select sum(balance) from account;
+--------------+
| sum(balance) |
+--------------+
| 19300 |
+--------------+
1 row in set (0.00 sec)


  4.序列化

    (1)開啟一個客戶端A,並設定當前事務模式為serializable,查詢表account的初始值:


mysql> set session transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)


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


mysql> select * from account;
+------+--------+---------+
| id   | name   | balance |
+------+--------+---------+
|    1 | lilei  |   10000 |
|    2 | hanmei |   10000 |
|    3 | lucy   |   10000 |
|    4 | lily   |   10000 |
+------+--------+---------+
4 rows in set (0.00 sec)


		

    (2)開啟一個客戶端B,並設定當前事務模式為serializable,插入一條記錄報錯,表被鎖了插入失敗,mysql中事務隔離級別為serializable時會鎖表,因此不會出現幻讀的情況,這種隔離級別併發性極低,往往一個事務霸佔了一張表,其他成千上萬個事務只有乾瞪眼,得等他用完提交才可以使用,開發中很少會用到。

mysql> set session transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)

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

mysql> insert into account values(5,'tom',0);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

 

 

 

  補充:

  1、SQL規範所規定的標準,不同的資料庫具體的實現可能會有些差異

  2、mysql中預設事務隔離級別是可重複讀時並不會鎖住讀取到的行

  3、事務隔離級別為序列化時,讀取資料會鎖住整張表

  4、閱讀此文時,如果站在開發者的角度,也許會覺得不可重複讀和幻讀,在邏輯上並沒有什麼問題,最終資料仍然是一致的,但是站在使用者的角度,他們通常只能看到一個事務(只能看到客戶端A,不知道客戶端B這個臥底的存在),而不會考慮事務併發執行的現象,一旦出現同一資料多次讀取結果不同,或者憑空出現新記錄,他們可能會產生疑慮,這是使用者體驗的問題。

  5.事務在mysql中執行時,最終的結果不會出現資料的一致性的問題,因為在一個事務中,mysql執行某個操作未必會使用前一個操作的中間結果,它會根據其他併發事務的實際情況採來處理,看起來不合邏輯,但是保證了資料的一致性 ;但是事務在應用程式中執行時,一個操作的結果會被下一個操作用到,並進行其他的計算。這是我們得小心,可重複讀的時候應該鎖行,序列化時 要鎖表,不然會破壞資料的一致性。

  6、事務在mysql中執行時,mysql會根據各個事務的實際情況綜合處理,導致資料的一致性沒有被破壞,但是應用程式時按照邏輯套路來出牌,並沒有mysql聰明,難免會出現資料的一致性問題。

  7、隔離級別越高,越能保證資料的完整性和一致性,但是對併發效能的影響也越大,魚和熊掌不可兼得啊。對於多數應用程式,可以優先考慮把資料庫系統的隔離級別設為Read Committed,它能夠避免髒讀取,而且具有較好的併發效能。儘管它會導致不可重複讀、幻讀這些併發問題,在可能出現這類問題的個別場合,可以由應用程式採用悲觀鎖或樂觀鎖來控制。






Innodb幻讀的各種姿勢

 2017-07-01 吳炳錫 

導讀


認識幻讀,瞭解在應用開發中,高併發情況下可能造成的資料不一致問題。


----MySQL支援事務隔離級別----

MySQL預設事務隔離級別: REPEATABLE READ 為了提高併發度建議調整為:READ COMMITTED.

MySQL支援事務隔離級別

  • 未提交讀(READ UNCOMMITTED):另一個事務修改了資料,但尚未提交,而本事務中的SELECT會讀到這些未被提交的資料(髒讀)。

  • 提交讀(READ COMMITTED):本事務讀取到的是最新的資料(其他事務提交後的)。問題是,在同一個事務裡,前後兩次相同的SELECT會讀到不同的結果(不重複讀)。

  • 可重複讀(REPEATABLE READ):在同一個事務裡,SELECT的結果是事務開始時時間點的狀態,因此,同樣的SELECT操作讀到的結果會是一致的。但是,會有幻讀現象。

  • 序列化(SERIALIZABLE):讀操作會隱式獲取共享鎖,可以保證不同事務間的互斥。(這個表級高了的序列控制,不是例項級別的)

  • ----重點名詞解析----

  • 髒讀: 最容易理解。 另一個事務修改的資料,還沒提交,而本事務中的SELECT語句讀取到資料。

  • 不可重複讀(提交讀): 把髒讀搞定後,可能會遇到另一個問題,提交讀。 事務中兩次同樣的SELECT語句讀取到不同的結果。

  • 幻讀(一致性讀):解決不可重複讀的問題後,可以保證同一個事情裡,查詢的結果都是事務開始的狀態(一致性)。但是,如果另一個事務同時提交了資料,本事務更新時,就會驚奇的發現,這些資料好象是之前讀到的。

三組實驗


一、準備實驗環境

create database zst;
use zst;
create table a1(
id int not null,
name varchar(32),
primary key (id)) engine=innodb;
確認事務隔離級別在rr下
mysql> select @@global.tx_isolation, @@tx_isolation;
+———————————-+————————-+
| @@global.tx_isolation | @@tx_isolation  |
+———————————-+————————-+
| REPEATABLE-READ       | REPEATABLE-READ |
+———————————-+————————-+

二、開始實驗

1、實驗一

session 1 session 2
begin; select * from a1; 
Empty set (0.01 sec)
begin;

insert into a1 values(1,’zhishutang.com’);
select * from a1;
Empty set (0.01 sec)


commit;
select * from a1;
Empty set (0.01 sec)

insert into a1 values(1,’wubx’);
ERROR 1062 (23000): Duplicate entry ‘1’ for key ‘PRIMARY’
_

實驗結果:查的沒資料,但實際寫入失敗!!! 還好是寫入失敗,因而不會影響太大。

2、實驗二

這次來一個更猛一點的:

session 1 session 2
truncate a1 ;
insert into a1 values(1,’a1’),(2,’a2’),(3,’a3’);

begin; begin;
select * from a1 select * from a1;
1, a1
2,   a2
3, a3 
3 rows in set (0.00 sec)
1, a1
2,   a2
3, a3 
3 rows in set (0.00 sec)

delete from a1 where id=1;
commit;
select * from a1 select * from a1;
1, a1
2,   a2
3, a3 
3 rows in set (0.00 sec)
2,   a2
3, a3 
2 rows in set (0.00 sec)
update a1 set name=’wubx’;
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2  Changed: 2  Warnings: 0

select * from a1 select * from a1;
1, a1
2,   wubx
3, wubx 
3 rows in set (0.00 sec)
2,   a2
3, a3 
2 rows in set (0.00 sec)
commit 
Query OK, 0 rows affected (0.00 sec)
_

實驗結果:天殺的update更新,提示成功,卻看到一行老資料,不管更新幾次,那個老資料還在。commit後消失原來那行老資料丟失。  

另外,該過程也適用於insert:

session 1 session 2
truncate a1 ;
begin; begin;
insert into a1 values(1,’a1’); insert into a1 values(2,’abc’);
select * from a1 select * from a1
1, a1 2,abc

commit
select * from a1 select * from a1
1, a1 2,abc
update a1 set name=’zhishutang.com’;
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2  Changed: 2  Warnings: 0

select * from a1 select * from a1
1, zhishutang.com
2, zhishutang.com
2.abc
rollback
select * from a1 select * from a1
2, abc 2, abc

實驗結果:在本操作中,Session 1是讀取到一條,但做一個次update更新後可以看到2條記錄,這個也可以說是事務的一個幻讀,但這個事務並沒有被破壞,rollback還能把Session 1的操作撤掉。

附:其實這種情形在官方手冊裡也有標註,如下:

https://dev.mysql.com/doc/refman/5.7/en/innodb-consistent-read.html
The snapshot of the database state applies to SELECT statements within a transaction, not necessarily to DML statements. If you insert or modify some rows and then commit that transaction, a DELETE or UPDATE statement issued from another concurrent REPEATABLE READ transaction could affect those just-committed rows, even though the session could not query them. If a transaction does update or delete rows committed by a different transaction, those changes do become visible to the current transaction.

3、實驗三

https://dev.mysql.com/doc/refman/5.7/en/innodb-consistent-read.html
If you want to see the “freshest” state of the database, use either the READ COMMITTED isolation level or a locking read:

session 1 session 2
insert into a1 values(1,’a1’); insert into a1 values(2,’abc’);
1, a1 2,abc

commit
select * from a1 select * from a1
1, a1 2,abc

commit
select * from a1
1,a1
select * from a1 lock in share mode;
1,a1 
2, abc

select * from a1 for update;
1,a1 
2, abc

select * from a1
1,a1 _

總結



1、在RR隔離級別下可以得到一致性的讀。

2、對於DML操作,容易出現幻讀問題,在程式設計時,需要注意並行設計這塊

3、事務處理中,為了得到新資料,也可以考慮利用顯式鎖的形式來獲取最新資料。

4、特別注意該表在事務操作中會Block DDL操作。—這個沒補實驗,各位自補。


更多資訊可參考以下連結:

https://dev.mysql.com/doc/refman/5.7/en/innodb-consistent-read.html






About Me

...............................................................................................................................

● 本文轉載自:http://www.cnblogs.com/huanongying/p/7021555.html

● 本文在itpub(http://blog.itpub.net/26736162)、部落格園(http://www.cnblogs.com/lhrbest)和個人微信公眾號(xiaomaimiaolhr)上有同步更新

● 本文itpub地址:http://blog.itpub.net/26736162/abstract/1/

● 本文部落格園地址:http://www.cnblogs.com/lhrbest

● 本文pdf版及小麥苗雲盤地址:http://blog.itpub.net/26736162/viewspace-1624453/

● 資料庫筆試面試題庫及解答:http://blog.itpub.net/26736162/viewspace-2134706/

● QQ群:230161599     微信群:私聊

● 聯絡我請加QQ好友(646634621),註明新增緣由

● 於 2017-07-01 09:00 ~ 2017-07-31 22:00 在魔都完成

● 文章內容來源於小麥苗的學習筆記,部分整理自網路,若有侵權或不當之處還請諒解

● 版權所有,歡迎分享本文,轉載請保留出處

...............................................................................................................................

拿起手機使用微信客戶端掃描下邊的左邊圖片來關注小麥苗的微信公眾號:xiaomaimiaolhr,掃描右邊的二維碼加入小麥苗的QQ群,學習最實用的資料庫技術。

【MySQL】MySQL的四種事務隔離級別
DBA筆試面試講解
歡迎與我聯絡

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/26736162/viewspace-2141789/,如需轉載,請註明出處,否則將追究法律責任。

相關文章