1. 事務概述
什麼是事務?
事務是邏輯上的一組操作,要麼都執行,要麼都不執行。
舉個轉賬栗子:
假如小明要給小紅轉賬 1000 元,這個轉賬會涉及到兩個關鍵操作就是:
- 将小明的餘額減少 1000 元
- 将小紅的餘額增加 1000 元。
事務會把這兩個操作就可以看成邏輯上的一個整體,這個整體包含的操作要麼都成功,要麼都要失敗。這樣就不會出現小明餘額減少而小紅的餘額卻并沒有增加的情況。
2. 什麼是資料庫事務
資料庫事務在我們日常開發中接觸的最多了。如果你的項目屬于單體架構的話,你接觸到的往往就是資料庫事務了。
平時,我們在談論事務的時候,如果沒有特指分布式事務,往往指的就是資料庫事務。
那資料庫事務有什麼作用呢?
簡單來說:資料庫事務可以保證多個對資料庫的操作(也就是 SQL 語句)構成一個邏輯上的整體。構成這個邏輯上的整體的這些資料庫操作遵循:要麼全部執行成功,要麼全部不執行 。
如:
# 開啟一個事務
START TRANSACTION;
# 多條 SQL 語句
SQL1,SQL2...
## 送出事務
COMMIT;
另外,關系型資料庫(例如:
MySQL
、
SQL Server
、
Oracle
等)事務都有 ACID 特性:
3. 事務的ACID特性
- 原子性(
) : 事務是最小的執行機關,不允許分割。事務的原子性確定動作要麼全部完成,要麼完全不起作用;
Atomicity
- 一緻性(
): 執行事務前後,資料保持一緻,例如轉賬業務中,無論事務是否成功,轉賬者和收款人的總額應該是不變的;
Consistency
- 隔離性(
): 并發通路資料庫時,一個使用者的事務不被其他事務所幹擾,各并發事務之間資料庫是獨立的;
Isolation
- 持久性(
): 一個事務被送出之後。它對資料庫中資料的改變是持久的,即使資料庫發生故障也不應該對其有任何影響。
Durability
資料事務的實作原理呢?
以 MySQL 的 InnoDB 引擎為例
MySQL InnoDB 引擎使用 redo log(重做日志) 保證事務的持久性,使用 undo log(復原日志) 來保證事務的原子性。
MySQL InnoDB 引擎通過 鎖機制、MVCC 等手段來保證事務的隔離性( 預設支援的隔離級别是
REPEATABLE-READ
)。
保證了事務的持久性、原子性、隔離性之後,一緻性才能得到保障。
4. 并發事務帶來的問題
在典型的應用程式中,多個事務并發運作,經常會操作相同的資料來完成各自的任務(多個使用者對同一資料進行操作)。
并發雖然是必須的,但可能會導緻以下的問題。
- 髒讀(Dirty read): 當一個事務正在通路資料并且對資料進行了修改,而這種修改還沒有送出到資料庫中,這時另外一個事務也通路了這個資料,然後使用了這個資料。因為這個資料是還沒有送出的資料,那麼另外一個事務讀到的這個資料是“髒資料”,依據“髒資料”所做的操作可能是不正确的。
- 丢失修改(Lost to modify): 指在一個事務讀取一個資料時,另外一個事務也通路了該資料,那麼在第一個事務中修改了這個資料後,第二個事務也修改了這個資料。這樣第一個事務内的修改結果就被丢失,是以稱為丢失修改。 例如:事務 1 讀取某表中的資料 A=20,事務 2 也讀取 A=20,事務 1 修改 A=A-1,事務 2 也修改 A=A-1,最終結果 A=19,事務 1 的修改被丢失。
- 不可重複讀(Unrepeatable read): 指在一個事務内多次讀同一資料。在這個事務還沒有結束時,另一個事務也通路該資料。那麼,在第一個事務中的兩次讀資料之間,由于第二個事務的修改導緻第一個事務兩次讀取的資料可能不太一樣。這就發生了在一個事務内兩次讀到的資料是不一樣的情況,是以稱為不可重複讀。
- 幻讀(Phantom read): 幻讀與不可重複讀類似。它發生在一個事務(T1)讀取了幾行資料,接着另一個并發事務(T2)插入了一些資料時。在随後的查詢中,第一個事務(T1)就會發現多了一些原本不存在的記錄,就好像發生了幻覺一樣,是以稱為幻讀。
不可重複讀和幻讀差別:
不可重複讀的重點是修改比如多次讀取一條記錄發現其中某些列的值被修改,幻讀的重點在于新增或者删除比如多次讀取一條記錄發現記錄增多或減少了。
5. 事務的隔離級别
SQL 标準定義了四個隔離級别:
- READ-UNCOMMITTED(讀取未送出): 最低的隔離級别,允許讀取尚未送出的資料變更,可能會導緻髒讀、幻讀或不可重複讀。
- READ-COMMITTED(讀取已送出): 允許讀取并發事務已經送出的資料,可以阻止髒讀,但是幻讀或不可重複讀仍有可能發生。
- REPEATABLE-READ(可重複讀): 對同一字段的多次讀取結果都是一緻的,除非資料是被本身事務自己所修改,可以阻止髒讀和不可重複讀,但幻讀仍有可能發生。
- SERIALIZABLE(可串行化): 最高的隔離級别,完全服從 ACID 的隔離級别。所有的事務依次逐個執行,這樣事務之間就完全不可能産生幹擾,也就是說,該級别可以防止髒讀、不可重複讀以及幻讀。
隔離級别 | 髒讀 | 不可重複讀 | 幻讀 |
---|---|---|---|
READ-UNCOMMITTED | √ | √ | √ |
READ-COMMITTED | × | √ | √ |
REPEATABLE-READ | × | × | √ |
SERIALIZABLE | × | × | × |
6. MySQL的預設隔離級别
MySQL InnoDB 存儲引擎的預設支援的隔離級别是 REPEATABLE-READ(可重讀)。我們可以通過
SELECT @@tx_isolation;
指令來檢視,MySQL 8.0 該指令改為
SELECT @@transaction_isolation;
mysql> SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+ Copy to clipboardErrorCopied
這裡需要注意的是:與 SQL 标準不同的地方在于 InnoDB 存儲引擎在 REPEATABLE-READ(可重讀) 事務隔離級别下使用的是 Next-Key Lock 鎖算法,是以可以避免幻讀的産生,這與其他資料庫系統(如 SQL Server)是不同的。是以說 InnoDB 存儲引擎的預設支援的隔離級别是 REPEATABLE-READ(可重讀) 已經可以完全保證事務的隔離性要求,即達到了 SQL 标準的 SERIALIZABLE(可串行化) 隔離級别。
🐛 問題更正:MySQL InnoDB 的 REPEATABLE-READ(可重讀)并不保證避免幻讀,需要應用使用加鎖讀來保證。而這個加鎖度使用到的機制就是 Next-Key Locks。
因為隔離級别越低,事務請求的鎖越少,是以大部分資料庫系統的隔離級别都是 READ-COMMITTED(讀取送出内容) ,但是你要知道的是 InnoDB 存儲引擎預設使用 REPEATABLE-READ(可重讀) 并不會有任何性能損失。
InnoDB 存儲引擎在 分布式事務 的情況下一般會用到 SERIALIZABLE(可串行化) 隔離級别。
🌈 拓展一下(以下内容摘自《MySQL 技術内幕:InnoDB 存儲引擎(第 2 版)》7.7 章):
InnoDB 存儲引擎提供了對 XA 事務的支援,并通過 XA 事務來支援分布式事務的實作。分布式事務指的是允許多個獨立的事務資源(transactional resources)參與到一個全局的事務中。事務資源通常是關系型資料庫系統,但也可以是其他類型的資源。全局事務要求在其中的所有參與的事務要麼都送出,要麼都復原,這對于事務原有的 ACID 要求又有了提高。另外,在使用分布式事務時,InnoDB 存儲引擎的事務隔離級别必須設定為 SERIALIZABLE。