【MySQL】事務隔離級別及ACID
- 2019 年 10 月 25 日
- 筆記
註: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。