POABOB

小小工程師的筆記分享

0%

關聯式資料庫中的 ACID

前言

在關聯式資料庫中,我們肯定會在資料異動的時候使用 Transaction 來確保資料的正確性,透過 ACID 的特性,才得以讓關聯式資料庫有如此穩定的操作,避免種種的問題發生。

本篇文章將會詳細介紹 ACID 的特性,並且分析每個特性的原理和解決方式。

定義

ACID 是由 AtomicityConsistencyIsolationDurability 四個特性所構成的,其中主要應用於 RDBMS 執行 Transaction 的過程。

Atomicity 原子性

每筆 Transaction 內都會包含 至少一筆操作,該特性會確保 Transaction 內這些操作不是 全部成功,就是 全部失敗

a

舉例來說

假設 Alice 要透過網路銀行轉帳 100 元給 Bob,執行時必須確保 Alice 有確實扣款,而 Bob 有實際收款,那麼 Atomicity 就可以完美的確保執行操作的時候不會漏掉任何一方。

b

MySQL 為例,在執行的時候,把需要執行的語句放在 START TRANSACTIONCOMMIT 中,就可以確保這些操作有原子性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 開啟 TRANSACTION
START TRANSACTION;

SELECT @A := money FROM account WHERE name = "Alice" LIMIT 1;

IF (@A - 100) > 0 THEN
UPDATE account SET money = (@A - 100) WHERE name = "Alice";
SELECT @B:=money FROM account WHERE name = "Bob" LIMIT 1;
UPDATE account SET money = (@B + 100) WHERE name = "Bob";
INSERT INTO log(from, to, type, amount) VALUES("Alice", "Bob", "transfer", 100);
END IF;

# 結束 TRANSACTION,確認操作成功
COMMIT;

Undo Log

InnoDB engine 用來執行 rollback 的一個 log,主要實現了 原子性 的功能,每當 Transaction 中有任何一筆操作 (新增、更新、刪除),就會將需要 rollback 的資訊記錄到 undo log 內,一旦發生了 rollback,就會讀取 undo log 的資料,並執行與原先相反的操作來還原原本資料。

c

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 才有辦法完成讀取。

d

不可重複讀 Non-repeatable Read

例子

當 Transaction A 第一次讀取資料 $100 被 Transaction B 所更新,最後 Transaction A 再度讀取資料 $200,Transaction A 兩次讀取的資料都不相同,稱之為 不可重複讀

解決方式

將資料庫隔離級別設定為 Repeatable Read (MySQL 預設隔離機制),設定後 MySQL 會先去 鎖定要讀取的**該行**,直到 Transaction A 完成後才會讓 Transaction B 進行更新。

e

幻讀 Phantom Read

例子

當 Transaction A 第一次讀取使用者人數 (500 人) 後被 Transaction B 所新增 700 人,最後 Transaction A 再度讀取人數發現變成 1200 人,Transaction A 兩次讀取的資料數量都不相同,稱之為 幻讀

幻讀不可重複讀 的差別是,幻讀是針對 新增或刪除 的操作所產生的 整體改動,不可重複讀則是針對 更新 某幾行資料所產生的 局部改動

解決方式

將資料庫隔離級別設定為 Serializable,設定後 MySQL 會先去 鎖定要讀取的**整張表**,直到 Transaction A 完成後才會讓 Transaction B 進行更新。

f

以下是 資料庫的隔離級別 對應可以 避免 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 CommittedRepeatable 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 的紀錄,將更改的資料寫入硬碟。

g

結論

透過詳細介紹 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
------ 本文結束 ------