一、概念
事務 一般指的是邏輯上的一組操作,或者作爲單個邏輯單元執行的一系列操作,一個事務中的所有操作會被封裝成一個不可分割的執行單元,這個單元的所有操作要麼全部執行成功,要麼全部執行失敗,只要其中任意一個操作執行失敗,整個事務就會執行回滾操作。
二、事務的特性以及類型介紹
2.1 事務特性
原子性(atomicity)
事務的原子性指的是構成事務的所有操作要麼全部執行成功,要麼全部執行是失敗。
一致性(consistency)
事務的一致性指的是事務執行之前和執行之後,數據始終處於一致的狀態。
隔離性(isolation)
事務的隔離性指的是併發執行的兩個事務之間互不干擾,也就是說,一個事務執行的過程中是無法看到其他事務運行過程的中間狀態的。
👨注意:
MySQL
是通過鎖個MVCC
機制來保證事務的隔離性。
持久性(duration)
事務的持久性指的是一旦事務被提交後,此事務對數據的更改操作會被持久化到數據庫中,並且不會被回滾。
2.2 兩種事務類型介紹
- 本地事務
- 分佈式事務
本地事務
通常基於關係型數據庫控制的事務可以稱作爲傳統事務或者本地事務。
本地事務的執行流程
- 客戶端開始事務操作之前,需要開啓一個連接回話;
- 開始回話之後,客戶端發起開啓事務的指令;
- 事務開始後,客戶端發送各種SQL語句處理數據;
- 正常情況下,客戶端會發起提交事務的指令,如果發生異常情況,客戶端會發起回滾事務命令;
- 上述流程完成後,關閉會話。
✔本地事務是有資源管理器在本地進行管理的。
本地事務的缺點在於:
- 不具備分佈式事務的處理能力
- 一次事務過程只能連接一個支持事務的數據庫,既不能用於多個事務性數據庫。
三、併發事務會帶來的哪些問題?
更新丟失(髒寫)
當兩個會在兩個以上的事務同時操作同一行數據,並給予最初選定的值更新該行數據時,視爲事務之間是無法感知彼此的存在的,所以會出現最後的更新操作會覆蓋之前其他事務完成的更新操作。
舉個例子:
張三的賬戶爲100元,當前有兩個事務:事務1和事務2,事務1是將張三賬戶餘額增加100元,事務2是將張三餘額增加200,起初事務1和事務2同時讀到張三的賬戶餘額都是100元,然後事務1和事務2分別更新張三月,假設事務1先於事務2提交,但是最近兩個事務都提交後張三的餘額爲300元(正常情況應該是400元),也就是說:後提交的事務2覆蓋了事務1的更新操作,這就是所謂的更新丟失,更新丟失(髒寫)本質上是寫操作的衝突,然而解決髒寫的方式是讓每個事務串行方式執行,保證事務按照一定的順序執行寫操作。
髒讀
一個事務讀取到另一個事務未提交的數據。比如:事務1正在對張三的餘額增加100元,在這個事務沒提交之前,另一個事務2讀取了正在修改的這條數據,如果沒有事務的控制下,第二條事務就會讀取到沒有被提交的髒數據,並且對髒數據叢下一步的處理,此時就會產生未提交數據的依賴關係,通常這種現象被稱爲 髒讀
,也就是說:髒讀是一個事務讀取了另一個事務沒提交的數據。
🤔髒讀本質上是讀寫操作的衝突,解決方法是先寫後讀,也就是寫完之後再讀。
不可重複讀
一個事務讀取了某些數據,在一段時間後,這個事務再次讀取之前讀過的數據,此時發現讀取的數據發生了變化,或者其中某些數據記錄已經被刪除,這種現象被稱爲:不可重複讀,即同一個事務,使用同樣的查詢語句,在不同時刻讀取到的結果不一致。
不可重複讀的本質上也是讀寫操作衝突,解決方法是先讀後寫,也就是讀完之後再寫。
幻讀
一個事務按照相同的查詢條件重新讀取之前讀過的數據,此時發現其他事務插入了滿足當前查詢條件的新數據,數據結果集變多,這種現象稱爲幻讀,即一個事務兩次讀取一個範圍的數據記錄,兩次讀到的結果不同。
幻讀的本質上也是讀寫操作衝突,解決方法是先讀後寫,也就是讀完之後再寫。
那到這裏,很多小夥伴就有疑問,同樣本質都是讀寫操作衝突,解決方法是先讀後寫,不可重複讀和幻讀到底有何區別?,我下面見簡單解釋一下:
1️⃣ 不可重複讀的重點在於更新和刪除操作,而幻讀的重點在於插入操作。
2️⃣ MySQL使用鎖機制實現事務的隔離級別時,在可重複讀隔離級別中,SQL語句第一個讀取到數據後,會將相應的數據加鎖,使得其他事務無法修改和刪除這些數據,通過鎖的方式實現可重複讀。但是這種方法無法對新數據的插入加鎖,如果事務1讀取了數據,或者修改和刪除了數據,事務2還可以進行插入操作,導致事務1莫名其妙地多了一條之前沒有的數據,這就是幻讀。
3️⃣ 所以,幻讀是無法通過鎖機制來避免,需要使用串行化的事務隔離級別,但是這種事務隔離級別會大大降低數據庫的併發能力。
✔MySQL是通過MVCC(多版本併發控制)機制來避免不可重複讀和幻讀的。
四、MySQL事務的隔離級別
使用下面的命令可以查詢全局級別和會話級別的事務隔離級別:
# 默認數據庫的事務隔離級別爲:可重複讀(REPEATABLE-READ)
SELECT @@global.tx_isolation;
SELECT @@session.tx_isolation;
SELECT @@tx_isolation;
五、多版本併發控制MVCC
Multi-Version Concurrency Control
多版本併發控制,MVCC
是一種併發控制的方法,一般在數據庫管理系統中,實現對數據庫的併發訪問。
MVCC比鎖的優勢
可以將MVCC
看成行級別鎖的一種妥協,它在許多情況下避免了使用鎖,同時可以提供更小的開銷。根據實現的不同,它可以允許非阻塞式讀,在寫操作進行時只鎖定必要的記錄。
MVCC的實現原理
MVCC
的是通過保存數據澡某個時間點的快照來實現的,也就是說,不管事務執行多長的時間,每個事務看到的數據都是一致的。根據事務的開始時間不同,每個事務對同一張表,同一時刻看到的數據可能是不一樣的。InnonDB
主要通過爲每一行記錄添加兩個額外的隱藏的值來實現MVCC
,這兩個值一個記錄這行數據何時被創建,另外一個記錄這行數據何時過期(或者被刪除)。但是InnoDB
並不存儲這些事件發生時的 實際時間 ,相反它只存儲這些事件發生時的系統 版本號(version) 。這是一個隨着事務的創建而不斷增長的數字。每個事務在事務開始時會記錄它自己的系統版本號。每個查詢必須去檢查每行數據的版本號與事務的版本號是否相同。
《高性能MySQL》書籍中介紹到:
MVCC
只在REPEATABLE READ
和READ COMMITTIED
兩個隔離級別下工作,其他兩個隔離級別都和MVCC
不兼容,因爲READ UNCOMMITTED
總是讀取最新的數據行,而不符合當前事務版本數據行,而SERIALIZABLE
串行化隔離級別則會對所有讀取的行都加鎖。
接下來舉個例子說明在可重複讀事務隔離級別下,MVCC
機制是如何完成增刪改查操作的。
- 查詢操作(SELECT)
在查詢操作中,InnoDB存儲引擎跟根據以下兩個條件查詢對應的行記錄,只有滿足對應條件纔會被返回:
1️⃣ 只查找不晚於當前事務版本的數據行,也就是說,InnoDB存儲引擎只會查找版本號小於或者等於當前事務版本的數據行,這些數據行要麼在該事務開始前就存在,要麼就是事務本身插入或者更新的行。
2️⃣ 對於數據刪除,數據行刪除的版本要麼還沒有被定義,要麼大於當前事務的版本號,只有這樣才能確保事務讀到的行,在事務開始前並沒有被刪除。
舉個例子,存在 事務A 和 事務B 兩個事務,事務A中存在兩套相同的SELECT
語句,事務B存在一條UPDATE
語句,事務A 的第一條查詢語句在事務B提交之前執行,第二條查詢語句在 事務B 提交之後執行,事務A 如下所示:
-- 事務A操作
START TRANSACTION;
SELECT * FROM account WHERE id = 1; //在事務B提交之前執行
SELECT * FROM account WHERE id = 1; //在事務B提交之後執行
COMMIT;
事務B:
-- 事務B操作
START TRANSACTION;
UPDATE account SET balance = balance+100 WHERE id = 1;
COMMIT;
✔結論:如果沒有使用MVCC機制,則事務A中的第一條SELECT語句讀取的數據是修改前的數據,而第二條SELECT語句讀取的是修改後的數據,兩次讀取的數據不一致,想想,那不就亂了嗎😨? 如果使用了MVCC機制,無論事務B如何修改數據,事務A的兩條查詢語句的到的結果始終是一致的。
- 插入操作(SELECT)
在插入操作中,InnoDB
會將新插入的每一條行記錄的當前系統版本包保存爲行版本號。
比如:向account
表插入一條數據,同時MVCC
的兩個版本號分別爲create_version
和delete_version
,create_version
代表創建行的版本號,delete_version
代表刪除行的版本號,另外還有一個事務ID字段,如下面所示:
INSERT INTO account(id, name, balance) values(1001, 'austin', 100);
複製代碼
對應的版本號信息如下表:
id | name | balance | transaction_id | create_version | delete_version |
---|---|---|---|---|---|
1001 | austin | 100 | 1 | 1 | 未定義 |
可以看出,當向數據表新增記錄時,需要設置保存行的版本號,而刪除行的版本號未定義。
- 更新操作(SELECT)
在更新操作中,InnoDB
存儲引擎會插入一行新記錄,並保存當前系統的版本號爲新記錄行的版本號,同時保存當前系統的版本號到原來數據行作爲刪除標識。比如:將account數據表中id爲1001的用戶賬戶月增加100元,對應SQL如下:
UPDATE account SET balance = balance+100 WHERE id = 1001;
複製代碼
執行SQL, 在MVCC機制下的更新操作如下表所示:
id | name | balance | transaction_id | create_version | delete_version |
---|---|---|---|---|---|
1001 | austin | 100 | 1 | 1 | 2 |
1001 | austin | 200 | 2 | 2 | 未定義 |
可以明顯看出,執行更新操作時,MVCC
機制是先將原來的數據複製一份,將balance
字段增加100後,再講create_version
字段的值設置爲當前系統的版本號,而delete_version
字段的值未定義。
注意的是:原來的行會被複制到Undo Log中。
- 刪除操作(SELECT)
在刪除操作中,InnoDB存儲引擎會保存刪除的每一個行記錄當前的系統版本號,作爲刪除標識。比如:刪除account數據表中id爲1001的數據,SQL如下所示:
DELETE FROM account WHERE id = 1001;
複製代碼
對應MVCC機制下的刪除操作如下表所示:
id | name | balance | transaction_id | create_version | delete_version |
---|---|---|---|---|---|
1001 | austin | 200 | 3 | 2 | 3 |
可以看出,當刪除數據表數據行時,MVCC機制會將當前系統的版本號寫入刪除數據行版本字段delete_version中,以此來表示當前數據行已經被刪除。
六、總結
本文主要講述了事務的特性、類型和本地事務和分佈式事務的區別,通過不同的示例解釋併發事務帶來的更新丟失、髒讀、不可重複讀、幻讀的問題,同時介紹了多版本併發控制MVCC的實現原理。