1. 程式人生 > >【MySQL】事務隔離級別及ACID

【MySQL】事務隔離級別及ACID

注:begin或start transaction並不是一個事務的起點,而是在執行它們之後的第一個操作InnoDB表的語句,事務才真正開始。start transaction with consistent snapshot命令可以馬上啟動一個事務。

1、隔離級別

1.1、基本概念

讀未提交

當前事務能讀取到其他事務尚未提交的修改值。

讀提交

當前事務能讀取到其他事務已經提交的修改值。

可重複讀

一個事務在任何時刻查詢到的結果跟事務一開始啟動時查詢的結果一樣。

序列化

強行使所有事務序列執行。

1.2、小測試一

理解了隔離級別的基本概念後,我們做個小測試,看看是不是真的理解了,下面兩個會話按順序執行,最後在各個隔離級別下的V1、V2值各為多少呢?

sessionA sessionB
啟動事務
查詢得到值1
啟動事務
把值1更新成值2
查詢得到值V1
提交事務
查詢得到值V2
提交事務
查詢得到值V3

讀未提交可以讀取到其他事務未提交的值,所以V1讀取到了sessionB未提交的更改,值為2。
讀提交不能讀取到其他事務未提交的值,所以V1的值為1,當查詢V2時,sessionB已經提交了,這時讀取到的V2為2。
可重複讀查詢得到的值為事務啟動時的值,所以V1和V2值都為1。
序列化時,sessionA啟動事務後,sessionB會被卡住,直至sessionA的事務提交後才執行,所以V1和V2都是1,V3查詢時sessionB已經提交事務,所以V3的值為2。

V1 V2 V3
讀未提交 2 2 2
讀提交 1 2 2
可重複讀 1 1 2
序列化 1 1 2

1.3、小測試二

上面的小測試一加深了我們對事務隔離級別的瞭解,下面我們針對可重複讀隔離級別下對小測試一進行修改再加深理解。例子如下:

sessionA sessionB
啟動事務
查詢得V值為1
啟動事務
set V=V+1
查詢得到值V1
提交事務
set V=V+1
查詢得到值V2
提交事務
查詢得到值V3

小測試一可重複讀隔離級別下V1、V2的值都為1,V3的值為2。根據小測試一的思路,你可能會得出小測試二的結果為V1為1,V2為2,那麼V3是多少呢?

其實正確的答案是V1為1,V2為3,V3為3。如果sessionA中的set V=V+1是set V=1+1的話,這樣就丟失了sessionB更新的操作,使資料不正確,這時因為sessionB提交了,所以sessionA中的更新同一個值為當前讀,為set V=2+1,所以V2為3。小測試一中讀取的資料在概念中為一致性讀
如果流程中把sessionB的提交事務放在sessionA的提交事務後面呢?這個時候sessionA的set V=V+1會被阻塞,直至sessionB的事務提交後才會執行,結果還是V1為1,V2為3,V3為3。

MySQL的事務隔離級別預設為可重複讀,oracle的事務隔離級別預設為讀提交,所以在把oracle遷移至MySQL時需要記得把MySQL的設定讀提交後操作。
可重複讀在事務啟動時會建立一個檢視,後繼查詢都在此檢視上操作。
讀提交在事務啟動時也會建立一個檢視,但其他事務更新操作會直接更新此檢視。
讀未提交直接讀取資料庫中最新的資料。
序列化直接進行加鎖避免其他事務修改。

view1
view2
1 2

如上表格所示檢視,行資料從1更新為2的過程對應檢視view1到view2的過程。
讀提交隔離級別下,事務啟動時是view1檢視,當sessionB發生更新資料後,檢視view1會更新成檢視view2,後續查詢在view2上進行。
可重複讀隔離級別下,事務啟動時是view1檢視,當sessionB發生更新資料後,事務還是照樣讀取view1檢視。
可重複讀隔離級別下,是怎麼找到view1檢視的呢?
其實這就是MVCC的功能了,事務在啟動時會獲得系統會分配一個按順序遞增的transaction id,然後事務在更新資料時會把這個transaction id 分配給這一行資料當做trx row_id,順便記錄undo log日誌。比如上面sessionA事務在啟動時拿到的transaction id為889,sessionB的transaction id為890,那麼sessionB更新完後資料的trx row_id為890。在查詢V2的值時,sessionA發現數據版本為890,當前自己事務id為889,所以利用undo log“回滾”顯示出上一版本的資料,上一版本可以是889也可能是比889小的trx row_id,取值。

1.4、隔離級別解決髒讀、不可重複讀、幻讀

宣告一點,讀未提交、讀提交、可重複讀、序列化隔離級別依次遞增,隔離得越嚴實,效率就越低。
髒讀

事務讀取到其他事務修改後未提交的值。

不可重複讀

事務在啟動時讀取到的值,跟事務在執行過程中重新讀取到的值不一樣。

幻讀

事務啟動後,其他事務對資料庫新插入的行被本事務讀取到了。幻讀僅專指新插入的行。

根據上述髒讀、不可重複讀、幻讀的概念與隔離級別能做到的,我可以得出以下關係:

髒讀 不可重複讀 幻讀
讀未提交 可能 可能 可能
讀提交 不可能 可能 可能
可重複讀 不可能 不可能 可能
序列化 不可能 不可能 不可能

2、ACID

A.Atomicity.原子性

一個事務的操作是要麼全部成功,要麼全部失敗。例如A給B轉賬100塊,保證A賬戶會減少100塊,B賬戶增加100塊,這100塊是A轉過來的,不出現A賬戶減少了,B賬戶卻沒對應增加100塊的情況。

原子性通過undo log實現,undo log記錄了回滾資訊,當事務失敗或進行rollback時,就會根據資訊回滾到操作前的樣子。
當delete時,undo log會相應記錄被刪除資料的資訊,當發生回滾時,進行insert操作。
當update時,undo log會記錄更新操作的資訊,當發生回滾時,進行update操作。
當insert時,undo log會記錄對應的主鍵,當發生回滾時,進行delete操作。

C.Consistency.一致性

事務在執行前後,資料從一種合法的狀態,轉變成另一種合法的狀態。怎麼樣才算合法,都是可以自己定義的,比如事務啟動時A使用者賬戶了1000塊,B使用者有500塊,這個時候是啟動時合法的狀態。A使用者向B使用者轉賬100塊,A使用者賬戶餘額變成900,B變成600,這是結束時合法的狀態。或者平臺會扣除1%的手續費,轉賬完成後A使用者餘額為899,B使用者變成600,平臺賬戶增加1塊錢,這也是合法的狀態。

一致性是通過原子性、隔離性、永續性來保證的。所以只有保證了AID特性,才能保證C特性。

I.Isolation.隔離性

一個事務的查詢結果不會被其他事務所影響,事務之間是隔離的。例如X事務在啟動時查詢id=1的結果為1,X事務在沒有對此記錄進行修改操作的時候,多次查詢結果還是為1,不管其他事務是否對此記錄進行了修改。

隔離性通過MVCC實現,具體實現可以參考上面中的小測試邏輯。

D.Durability.永續性

一個事務執行完成了,所修改的資料能儲存至資料庫,不管宕機還是突然斷電,資料庫重新啟動後查詢到的資料為事務操作過後的資料。

永續性通過redo log實現,MySQL中有個WAL的邏輯,就是編輯資料時,會先更新redo log日誌,再更新記憶體,最後再寫磁碟。寫redo log日誌是一個非常快的操作,而更新完記憶體後,寫進磁碟是很忙的。當還沒寫進磁碟時發生宕機等,MySQL重啟後會自動去對比redo log日誌與磁碟內容,把未刷進磁碟的資料刷進磁碟中,從而保證了資料的永續性。

3、擴充套件知識

undo log

回滾日誌記錄資料修改的逆向還原操作記錄。例如資料值從1按順序修改成了2、3、4。那麼回滾日誌就會按順序記錄:2->1,3->2,4->3

MVCC

Multi-Version Concurrency Control是MySQL資料庫中的多版本併發控制,就是同一條記錄在資料庫中是可以存在多個版本。依靠undo log與樂觀鎖實現。

樂觀鎖

樂觀地認為不會衝突,在最後需要修改資料的時候去拿一下鎖認證一下就好。常見的方法有版本號控制和時間戳控制。

悲觀鎖

悲觀的認為資料會被其他事務修改,所以在拿到資料時直接加上鎖,阻止其他事務進行修改。例如for update。