MySQL-事務隔離級別設置


加鎖研究:http://www.cnblogs.com/JohnABC/p/4377529.html

先了解下 第一類丟失更新、臟讀、不可重復讀、幻讀、第二類丟失更新

第一類丟失更新

撤銷一個事務時, 把其他事務已經提交的更新數據覆蓋(此情況在事務中不可能出現, 因為一個事務中修改時此記錄已加鎖, 必須等待此事務完成后另一個事務才可以繼續UPDATE)

臟讀

一個事務讀到另一個事務,尚未提交的修改,就是臟讀。這里所謂的修改,除了Update操作,不要忘了,還包括Insert和Delete操作。臟讀的后果:如果后一個事務回滾,那么它所做的修改,統統都會被撤銷。前一個事務讀到的數據,就是垃圾數據。

舉個例子:預訂房間。

有一張Reservation表,往表中插入一條記錄,來訂購一個房間。

事務1:在Reservation表中插入一條記錄,用於預訂99號房間。

事務2:查詢,尚未預定的房間列表,因為99號房間,已經被事務1預訂。所以不在列表中。

事務1:信用卡付款。由於付款失敗,導致整個事務回滾。 所以插入到Reservation 表中的記錄並不置為持久(即它將被刪除)。

現在99號房間則為可用。

所以,事務2所用的是一個無效的房間列表,因為99號房間,已經可用。如果它是最后一個沒有被預定的房間,那么這將是一個嚴重的失誤。

注:臟讀的后果很嚴重。

再例如

時間 取款事務 支票轉賬事務
T1 開始事務  
T2   開始事務
T3 查詢賬戶余額為1000元  
T4    
T5 取出100,把存款余額改為900元  
T6   查詢賬戶的存款余額為900元(臟讀)
T7 撤銷該事務,把存款余額恢復為1000元  
T8   匯入100元,把存款余額改為1000元
T9   提交事務

 

不可重復讀

在同一個事務中,再次讀取數據時【就是你的select操作】,所讀取的數據,和第1次讀取的數據,不一樣了。就是不可重復讀。

舉個例子:

事務1:查詢有雙人床房間。99號房間,有雙人床。

事務2:將99號房間,改成單人床房間。

事務1:再次執行查詢,請求所有雙人床房間列表,99號房間不再列表中了。也就是說,事務1,可以看到其他事務所做的修改。

在不可重復讀,里面,可以看到其他事務所做的修改,而導致2次的查詢結果不再一樣了。這里的修改,是提交過的。也可以是沒有提交的,這種情況同時也是臟讀。

如果,數據庫系統的隔離級別。允許,不可重復讀。那么你啟動一個事務,並做一個select查詢操作。查詢到的數據,就有可能,和你第2次,3次...n次,查詢到的數據不一樣。一般情況下,你只會做一次,select
查詢,並以這一次的查詢數據,作為后續計算的基礎。因為允許出現,不可重復讀。那么任何時候,查詢到的數據,都有可能被其他事務更新,查詢的結果將是不確定的。

注:如果允許,不可重復讀,你的查詢結果,將是不確定的。一個不確定的結果,你能容忍嗎?

幻讀

事務1讀取指定的where子句所返回的一些行。然后,事務2插入一個新行,這個新行也滿足事務1使用的查詢where子句。然后事務1再次使用相同的查詢讀取行,但是現在它看到了事務2剛插入的行。這個行被稱為幻象,因為對事務1來說,這一行的出現是不可思議的。

舉個例子:

事務1:請求沒有預定的,雙人床房間列表。

事務2:向Reservation表中插入一個新紀錄,以預訂99號房間,並提交。

事務1:再次請求有雙人床的未預定的房間列表,99號房間,不再位於列表中。

注:幻讀,針對的是,Insert操作。如果事務2,插入的記錄,沒有提交。那么同時也是臟讀。

第二類丟失更新

這是不可重復讀中的特例, 一個事務覆蓋另一個事務已提交的更新數據(以下會出現此情況)

時間 取款事務 支票轉賬事務
T1 開始事務  
T2   開始事務
T3 查詢賬戶余額為1000元  
T4   查詢賬戶余額為1000元
T5 取出100,把存款余額改為900元  
T6 提交事務  
T7   匯入100元,把存款余額改為1100元
T8   提交事務

 

 

No 事務1 事務2 備注
1 select * from tb2 where id=1;
<NO>    id      name    name2
1       1       a       a


2
update tb2 set name2='b' where id=1;
1 Rows updated!

3 select * from tb2 where id=1;
<NO>    id      name    name2
1       1       a       a

很好:沒有出現臟讀
4
commit
5 select * from tb2 where id=1;
<NO>    id      name    name2
1       1       a       a

很好:沒有出現不可重復讀
6 update tb2 set name='a1' where id=1;
1 Rows updated!


7 select * from tb2 where id=1;
<NO>    id      name    name2
1       1       a1      b

糟糕:name2的值怎么跟以前不一樣了。(即出現了不可重復讀)
8 commit
 

 

應該明白 什么狀況對什么加什么鎖(可以分別思考和測試每個隔離級別下臟讀、不可重復讀、幻讀的出現及不出現原因)

讀完下面的內容可以回頭來看此段:

  不可重復讀錯誤處理:通常可以用 set tran isolation level repeatable read 來設置隔離級別, 這樣事務 A 在兩次讀取表T中的數據時, 事務B如果企圖更改表T中的數據(細節到事務A讀取數據)時, 就會被阻塞, 直到事務A提交! 這樣就保證了, 事務A兩次讀取的數據的一致性

  幻讀錯誤處理:如果設置 repeatable read, 雖然可以防止事務B對數據進行修改, 但是事務B卻可以向表T中插入新的數據, 如何防止這個問題, 我們可以考慮設置最高的事務隔離級別 set tran isolation level serializable, 於是乎, 事務B就只能乖乖的等待事務A的提交, 才能向表T中插入新的數據, 從而避免了幻讀

 

不可重復讀與幻讀的區別:

臟讀的重點是讀另一個事務未提交的數據(假若那個事務RollBack, 則這數據就是無效的):某個事務已更新一份數據, 另一個事務在此時讀取了同一份數據, 由於某些原因, 前一個RollBack了操作, 則后一個事務所讀取的數據就會是不正確的

不可重復讀的重點是修改: 同樣的條件, 你讀取過的數據, 再次讀取出來發現值不一樣了

幻讀的重點在於新增或者刪除: 同樣的條件, 第1次和第2次讀出來的記錄數不一樣

當然, 從總的結果來看, 似乎兩者都表現為兩次讀取的結果不一致, 但如果你從控制的角度來看, 兩者的區別就比較大, 對於不可重復讀, 只需要鎖住滿足條件的記錄, 對於幻讀, 要鎖住滿足條件及其相近的記錄

 

rollback;或者commit;都會退出事務!

更新記錄的時候需要鎖!

 

InnoDB中 只要此行在別的事務中產生了鎖 本事務中就不允許進行修改 只能阻塞 等待別的事務提交

 

一般設置為Read Committed, 並且利用悲觀鎖(SELECT ... FOR UPDATE)或樂觀鎖(SET NUM=NUM+1, 而不是計算后再插入, 避免並發)來處理特殊情況

 

SQL標准定義了4類隔離級別,包括了一些具體規則,用來限定事務內外的哪些改變是可見的,哪些是不可見的。低級別的隔離級一般支持更高的並發處理,並擁有更低的系統開銷。
Read Uncommitted(讀取未提交內容)

       在該隔離級別,所有事務都可以看到其他未提交事務的執行結果。本隔離級別很少用於實際應用,因為它的性能也不比其他級別好多少。讀取未提交的數據,也被稱之為臟讀(Dirty Read)。
Read Committed(讀取提交內容)

       這是大多數數據庫系統的默認隔離級別(但不是MySQL默認的)。它滿足了隔離的簡單定義:一個事務只能看見已經提交事務所做的改變。這種隔離級別 也支持所謂的不可重復讀(Nonrepeatable Read),因為同一事務的其他實例在該實例處理其間可能會有新的commit,所以同一select可能返回不同結果。
Repeatable Read(可重讀)

       這是MySQL的默認事務隔離級別,它確保同一事務的多個實例在並發讀取數據時,會看到同樣的數據行。不過理論上,這會導致另一個棘手的問題:幻讀 (Phantom Read)。簡單的說,幻讀指當用戶讀取某一范圍的數據行時,另一個事務又在該范圍內插入了新行,當用戶再讀取該范圍的數據行時,會發現有新的“幻影” 行。InnoDB和Falcon存儲引擎通過多版本並發控制(MVCC,Multiversion Concurrency Control)機制解決了該問題。

Serializable(可串行化)
       這是最高的隔離級別,它通過強制事務排序,使之不可能相互沖突,從而解決幻讀問題。簡言之,它是在每個讀的數據行上加上共享鎖。在這個級別,可能導致大量的超時現象和鎖競爭。只要操作產生了鎖,就不允許其他事務讀取和修改!(可以看看加鎖處理分析http://hedengcheng.com/?p=771或者百度網盤http://pan.baidu.com/s/1mgN00Og)

         這四種隔離級別采取不同的鎖類型來實現,若讀取的是同一個數據的話,就容易發生問題。例如:

         臟讀(Drity Read):某個事務已更新一份數據,另一個事務在此時讀取了同一份數據,由於某些原因,前一個RollBack了操作,則后一個事務所讀取的數據就會是不正確的。

         不可重復讀(Non-repeatable read):在一個事務的兩次查詢之中數據不一致,這可能是兩次查詢過程中間插入了一個事務更新的原有的數據。

         幻讀(Phantom Read):在一個事務的兩次查詢中數據筆數不一致,例如有一個事務查詢了幾列(Row)數據,而另一個事務卻在此時插入了新的幾列數據,先前的事務在接下來的查詢中,就會發現有幾列數據是它先前所沒有的。

         在MySQL中,實現了這四種隔離級別,分別有可能產生問題如下所示:

隔離級別 是否出現第一類丟失更新 是否出現臟讀 是否出現不可重復讀 是否出現幻讀 是否出現第二類丟失更新
Serializable
Repeatable Read
Read Commited
Read Uncommited


下面,將利用MySQL的客戶端程序,分別測試幾種隔離級別。測試數據庫為test,表為tx;表結構:

id                               int

num

                              int

兩個命令行客戶端分別為A,B;不斷改變A的隔離級別,在B端修改數據。

 

SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SERIALIZABLE]

 

 

(一)、將A的隔離級別設置為read uncommitted(未提交讀)

 在B未更新數據之前:

客戶端A:

B更新數據:

客戶端B:

客戶端A:

        經過上面的實驗可以得出結論,事務B更新了一條記錄,但是沒有提交,此時事務A可以查詢出未提交記錄。造成臟讀現象。未提交讀是最低的隔離級別。

(二)、將客戶端A的事務隔離級別設置為read committed(已提交讀)

 在B未更新數據之前:

客戶端A:

B更新數據:

客戶端B:

客戶端A:

       經過上面的實驗可以得出結論,已提交讀隔離級別解決了臟讀的問題,但是出現了不可重復讀的問題,即事務A在兩次查詢的數據不一致,因為在兩次查詢之間事務B更新了一條數據。已提交讀只允許讀取已提交的記錄,但不要求可重復讀。

(三)、將A的隔離級別設置為repeatable read(可重復讀)

 在B未更新數據之前:

客戶端A:

B更新數據:

客戶端B:

客戶端A:

B插入數據:

客戶端B:

客戶端A:

       由以上的實驗可以得出結論,可重復讀隔離級別只允許讀取已提交記錄,而且在一個事務兩次讀取一個記錄期間,其他事務部的更新該記錄。但該事務不要求與其他 事務可串行化。例如,當一個事務可以找到由一個已提交事務更新的記錄,但是可能產生幻讀問題(注意是可能,因為數據庫對隔離級別的實現有所差別)。像以上 的實驗,就沒有出現數據幻讀的問題。

(四)、將A的隔離級別設置為 可串行化 (Serializable)

A端打開事務,B端插入一條記錄

事務A端:

事務B端:

因為此時事務A的隔離級別設置為serializable,開始事務后,並沒有提交,所以事務B只能等待。

事務A提交事務:

事務A端

事務B端

      

      serializable完全鎖定字段,若一個事務來查詢同一份數據就必須等待,直到前一個事務完成並解除鎖定為止 。是完整的隔離級別,會鎖定對應的數據表格,因而會有效率的問題。

 

利用悲觀鎖

 

 

時間 取款事務 支票轉賬事務
T1 開始事務  
T2   開始事務
T3 select * from ACCOUNTS where ID = 1 for update;
查詢賬戶余額為1000元;這條記錄被鎖定。
 
T4   select * from ACCOUNTS where ID = 1 for update;
執行該語句時,事務停下來等待取款事務解除對這條記錄的鎖定。
T5 取出100,把存款余額改為900元  
T6 提交事務  
T7   事務恢復運行,查詢結果顯示存款余額為900.這條記錄被鎖定。
T8   匯入100元,把存款余額改為1000元。
T9   提交事務

 

用圖展示隔離級別及對應缺陷:

  READ UNCOMMITTED 在該隔離級別下,所有事務都可以看到其它未提交事務的執行結果。如下圖所示: 

    缺陷:事務2查詢到的數據是事務1中修改但未提交的數據,但因為事務1回滾了數據,所以事務2查詢的數據是不正確的,因此出現了臟讀的問題。

  READ COMMITTED 在該隔離級別下,一個事務從開始到提交之前對數據所做的改變對其它事務是不可見的,這樣就解決在READ-UNCOMMITTED級別下的臟讀問題。但如果一個事務在執行過程中,其它事務的提交對該事物中的數據發生改變,那么該事務中的一個查詢語句在兩次執行過程中會返回不一樣的結果。如下圖所示: 


    缺陷:事務2執行update語句但未提交前,事務1的前兩個select操作返回結果是相同的。但事務2執行commit操作后,事務1的第三個select操作就讀取到事務2對數據的改變,導致與前兩次select操作返回不同的數據,因此出現了不可重復讀的問題。

  REPEATABLE READ 這是MySQL的默認事務隔離級別,能確保事務在並發讀取數據時會看到同樣的數據行,解決了READ-COMMITTED隔離級別下的不可重復讀問題。mysql的InnoDB存儲引擎通過多版本並發控制(Multi_Version Concurrency Control, MVCC)機制來解決該問題。在該機制下,事務每開啟一個實例,都會分配一個版本號給它,如果讀取的數據行正在被其它事務執行DELETE或UPDATE操作(即該行上有排他鎖),這時該事物的讀取操作不會等待行上的鎖釋放,而是根據版本號去讀取行的快照數據(記錄在undo log中),這樣,事務中的查詢操作返回的都是同一版本下的數據,解決了不可重復讀問題。其原理如下圖所示: 

    缺陷:雖然該隔離級別下解決了不可重復讀問題,但理論上會導致另一個問題:幻讀(Phantom Read)。正如上面所講,一個事務在執行過程中,另一個事物對已有數據行的更改,MVCC機制可保障該事物讀取到的原有數據行的內容相同,但並不能阻止另一個事務插入新的數據行,這就會導致該事物中憑空多出數據行,像出現了幻讀一樣,這便是幻讀問題。如下圖所示:

    缺陷:事務2對id=1的行內容進行了修改並且執行了commit操作,事務1中的第二個select操作在MVCC機制的作用下返回的仍是v=1的數據。但事務3執行了insert操作,事務1第三次執行select操作時便返回了id=2的數據行,與前兩次的select操作返回的值不一樣。需要說明的是,REPEATABLE-READ隔離級別下的幻讀問題是SQL標准定義下理論上會導致的問題,MySQL的InnoDB存儲引擎在該隔離級別下,采用了Next-Key Locking鎖機制避免了幻讀問題。Next-Key Locking鎖機制將在后面的鎖章節中講到。

  SERIALIZABLE 這是事務的最高隔離級別,通過強制事務排序,使之不可能相互沖突,就是在每個讀的數據行加上共享鎖來實現。在該隔離級別下,可以解決前面出現的臟讀、不可重復讀和幻讀問題,但也會導致大量的超時和鎖競爭現象,一般不推薦使用。

  轉自:http://xm-king.iteye.com/blog/770721

  參考:http://blog.csdn.net/u013235478/article/details/50625602

  更多測試:

    http://blog.csdn.net/wangsifu2009/article/details/6715120

    http://www.2cto.com/database/201412/359238.html


免責聲明!

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



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