前言
在關聯式資料庫中,我們肯定會在資料異動的時候使用 Transaction 來確保資料的正確性,透過 ACID 的特性,才得以讓關聯式資料庫有如此穩定的操作,避免種種的問題發生。
本篇文章將會詳細介紹 ACID 的特性,並且分析每個特性的原理和解決方式。
定義
ACID 是由 Atomicity
、Consistency
、Isolation
、Durability
四個特性所構成的,其中主要應用於 RDBMS 執行 Transaction 的過程。
Atomicity 原子性
每筆 Transaction 內都會包含 至少一筆操作
,該特性會確保 Transaction 內這些操作不是 全部成功
,就是 全部失敗
。
舉例來說
假設 Alice 要透過網路銀行轉帳 100 元給 Bob,執行時必須確保 Alice 有確實扣款
,而 Bob 有實際收款
,那麼 Atomicity
就可以完美的確保執行操作的時候不會漏掉任何一方。
以 MySQL
為例,在執行的時候,把需要執行的語句放在 START TRANSACTION
和 COMMIT
中,就可以確保這些操作有原子性。
1 | # 開啟 TRANSACTION |
Undo Log
InnoDB engine 用來執行 rollback 的一個 log
,主要實現了 原子性
的功能,每當 Transaction 中有任何一筆操作 (新增、更新、刪除),就會將需要 rollback 的資訊記錄到 undo log 內,一旦發生了 rollback,就會讀取 undo log 的資料,並執行與原先相反的操作來還原原本資料。
Consistency 一致性
在 Transaction 的改動中,資料的異動必須 滿足 constraint
(Primary Key、Foreign Key、Unique Key),確保資料始終處於一致的狀態,若是沒有遵守這些約束,該 Transaction 就會被認作失敗並 rollback。
Isolation 隔離性
隔離性主要是為了避免 同時好幾個 Transaction 操作相同的表或資料
,所導致產生 Race Condition
的情況發生,可能產生的情況有 髒讀(dirty read)
、不可重複讀(non-repeatable read)
、幻讀(phantom read)
。
髒讀 Dirty Read
例子
如果現在有兩筆 Transaction 正要讀取同筆資料的餘額,一旦 Transaction A 更新完的資料 $200 被 Transaction B 所讀取,但最後 Transaction A Rollback 讓餘額回復到 $100,Transaction B 手上拿到的卻是過期的資料 $200,稱之為 髒讀
。
解決方式
將資料庫隔離級別設定為 Read Commited
,這樣在 Transaction B 讀取的時候就必須等待 Transaction A 將該筆資料 Commit 才有辦法完成讀取。
不可重複讀 Non-repeatable Read
例子
當 Transaction A 第一次讀取資料 $100 被 Transaction B 所更新,最後 Transaction A 再度讀取資料 $200,Transaction A 兩次讀取的資料都不相同,稱之為 不可重複讀
。
解決方式
將資料庫隔離級別設定為 Repeatable Read
(MySQL 預設隔離機制),設定後 MySQL 會先去 鎖定要讀取的**該行**
,直到 Transaction A 完成後才會讓 Transaction B 進行更新。
幻讀 Phantom Read
例子
當 Transaction A 第一次讀取使用者人數 (500 人) 後被 Transaction B 所新增 700 人,最後 Transaction A 再度讀取人數發現變成 1200 人,Transaction A 兩次讀取的資料數量都不相同,稱之為 幻讀
。
幻讀
與不可重複讀
的差別是,幻讀是針對新增或刪除
的操作所產生的整體改動
,不可重複讀則是針對更新
某幾行資料所產生的局部改動
。
解決方式
將資料庫隔離級別設定為 Serializable
,設定後 MySQL 會先去 鎖定要讀取的**整張表**
,直到 Transaction A 完成後才會讓 Transaction B 進行更新。
以下是 資料庫的隔離級別
對應可以 避免 Race Condition
的問題:
Dirty Read | Non-repeatable Read | Phantom Read | |
---|---|---|---|
Read Uncommitted | O | O | O |
Read Committed | X | O | O |
Repeatable Read | X | X | O |
Serializable | X | X | X |
一般來說,不會特意去使用 Serializable
鎖定 整張表
來避免 幻讀
(效能太差了),大多數都是使用 Read Committed
和 Repeatable Read
作為其隔離的級別。
Durability 持久性
當 Transaction 被 Commit
過後,資料可有永久儲存於硬碟之中,除非硬碟損毀。
Redo log
redo log 是一個實體 log,用來記錄那些資料 Page 進行什麼樣的修改,只要每次 Transaction 被 Commit 過後,就會產生一或多筆 log 紀錄,這樣就算是執行完成了,之後後台的 thread 會將 Buffer Pool 的 資料 Page 更新至硬碟中。
這也就是所謂的 WAL (Write-Ahead Logging)
技術,將 操作先寫於 log
中,並且 等待合適的時間再將資料修改至硬碟
。
這樣一來,如果我們在完成一筆 Transaction 後系統突然當機,MySQL 還是可以透過 redo log 的紀錄,將更改的資料寫入硬碟。
結論
透過詳細介紹 ACID 的特性後,可以發現 RDBMS 與 NoSQL 所發展的特性不太相同,強調資料的 一致性
與 持久性
,根據 適當的業務場景
採用適合的資料庫才是最佳選擇,而不是因為 NoSQL 擁有高效的分散式處理能力,就盲目的去使用它。
參考資料
- https://xiaolincoding.com/mysql/log/how_update.html
- https://xiaolincoding.com/mysql/transaction/mvcc.html
- https://cloud.tencent.com/developer/article/1450773