iT邦幫忙

2023 iThome 鐵人賽

DAY 19
1
自我挑戰組

Hello SQL 初次見面你好系列 第 19

Day 19 SQL 中的隔離級別

  • 分享至 

  • xImage
  •  

在前幾天的文章中我們有大概提到 SQL 中有提供 4 種 隔離級別 (Isolation Level) 分別是

  1. Read uncommitted
  2. Read committed
  3. Repeatable read
  4. Serializable

今天我們就來深入探討為什麼 SQL 中需要隔離級別,以及他們分別適用在哪些場景

為什麼會需要隔離級別

在 SQL 中,當多個交易(transaction)並發地(同時)運行時,可能會產生一些問題

假設你有一個購物網站,使用者可以在網站上購買商品,每當一個使用者購買一個商品時,系統會執行一個交易來減少商品的庫存數量,那這時如果兩個交易同時運行,而且沒有適當的隔離,那麼可能都會讀取到商品 A 的庫存數量為 1。然後,兩個交易都可能會認為庫存充足,並同時減少庫存數量。最終結果可能是商品 A 的庫存數量變成了 -1,雖然應該只會發生在很小的機率上

那我們主要可以將並行交易(Concurrency Transactions)產生的問題分為下面 4 種

並行交易(Concurrency Transactions) 帶來的 4 種問題

1.寫偏斜 (Write Skew)

寫偏斜(Write Skew) 發生在兩個交易同時讀取相同的資料並試圖更新它時。這會導致數據的不一致

  • 以下圖為例
  1. 下圖有兩筆交易(Transaction) A 以及 B 在同時間發生
  2. 兩者都在對 id 為 1 的帳號進行扣款 100 元
  3. 但是因為並行交易(Concurrency Transactions) 的關係,所以最後兩者都認為自己對這個帳號扣款了 100 元,但是實際上帳號卻扣了 200 元

https://ithelp.ithome.com.tw/upload/images/20231004/20152148nFDgzPnqDB.png

2.髒讀 (Dirty Reads)

髒讀發生當一個交易讀取到另一個尚未提交的交易所作的更改

  1. 我們可以看到下圖中的交易(Transaction) A 總共做了 3 次查詢
  2. 第一次的查詢看到帳號 (id = 1) 的餘額為 1000
  3. 第二次的查詢看到帳號 (id = 1) 的餘額為 800 (因為這時交易 B 對帳號進行扣款 200,但是未提交)
  4. 第二次的查詢看到帳號 (id = 1) 的餘額又變為 1000 (因為這時交易 B 回滾(ROLLBACK)了,代表交易 B 沒有成功)

https://ithelp.ithome.com.tw/upload/images/20231004/20152148EbJ6FmOq6b.png

3.幻讀 (Phantom Reads)

幻讀發生在一個交易在讀取某個範圍的資料(ROW)時,另一個交易插入了一個新的資料(ROW),導致第一個交易再次讀取該範圍時看到了 幻影

  1. 在交易 A 的第一次查詢,我們查詢了帳號餘額為 5000 ~ 10000 的數量有幾筆,結果為 1 筆
  2. 這時交易 B 寫入一筆資料 (id = 2, balance = 9999) 並執行 COMMIT;
  3. 在交易 A 再去做第二次查詢,結果卻變為 2 筆

https://ithelp.ithome.com.tw/upload/images/20231004/20152148VZnixkIRgh.png

4.不可重複讀 (Non-Repeatable Reads)

不可重複讀發生在一個交易讀取同一行兩次,但由於另一個交易所作的更改而導致數據不一致

  • 和髒讀的不同之處在於是髒讀是讀到 未提交(Uncommitted) 的資料,而不可重複讀讀到的卻是已提交(Commited)的數據
  1. 下圖中交易A 第一次查詢 id = 5 的帳號餘額為 1000
  2. 後來交易B 更新帳號餘額為 700 並提交出去
  3. 下圖中交易A 第二次查詢帳號餘額變為 700

https://ithelp.ithome.com.tw/upload/images/20231004/20152148k6wqalJ9rL.png

SQL 標準中的隔離級別

所以看完了上述幾個因為並行交易(Concurrency Transactions)所產生的問題後,所以就輪到了 SQL 隔離級別的登場來解決這些問題

Read uncommitted

這是最低的隔離級別。在這個級別中,一個事務(Transaction)可以讀取另一個還未提交的事務的數據
在 PostgreSQL 是沒有 Read uncommitted 隔離級別

Read uncommitted 還是會發生下列的問題

  • 寫偏斜 (Write Skew)
  • 髒讀 (Dirty Reads)
  • 幻讀 (Phantom Reads)
  • 不可重複讀 (Non-Repeatable Reads)

優點:

  • 性能最高,因為不會對讀取操作加鎖,允許最高程度的並發

缺點:

  • 可能會讀到未提交的變更,導致“髒讀”
  • 不能保證讀取的數據的一致性和完整性

Read committed

這個級別保證一個事務只能讀取已經提交的事務的數據,這是許多資料庫系統的默認隔離級別

Read committed 還是會發生下列的問題

  • 寫偏斜 (Write Skew)
  • 幻讀 (Phantom Reads)
  • 不可重複讀 (Non-Repeatable Reads)

優點:

  • 一般可以避免“髒讀”,因為只能讀取已經提交的數據
  • 有更好的並發性能,相對於更高的隔離級別
  • 一般情況使用就足夠了,也是 PostgreSQL 預設的隔離級別

缺點:

  • 若有多個交易在同一時間對同一筆數據進行讀取和寫入,可能會發生問題(寫偏斜情形)

在 PostgreSQL 如何使用

-- 使用一般的 BEGIN 他預設就是使用 `Read committed` Isolation Level
BEGIN;

-- do something

COMMIT;

Repeatable read

在這個級別中,一個事務在整個過程中都可以看到一個一致的數據視圖。即使其他事務已經提交了新的數據,它仍然可以看到數據的舊版本

Repeatable read 還是會發生下列的問題

  • 寫偏斜 (Write Skew)
  • 幻讀 (Phantom Reads)

優點:

  • 可以避免 不可重複讀,因為同一個交易中多次讀取同一筆數據會看到相同的結果
  • 相對於較低的隔離級別提供了較好的一致性

缺點:

  • 可能會導致幻讀
  • 並發性能可能會受到影響,因為需要對讀取的數據加鎖

在 PostgreSQL 如何使用

BEGIN;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- do something

COMMIT;

Serializable

這是最高的隔離級別。它通過完全封鎖對相同數據的同時訪問來避免所有的並發問題

在這個級別中,可以完全解決上述提到的並行交易(Concurrency Transactions) 帶來的問題,因為他會讓所有交易序列化排程,來確保即使交易是並發執行的,它們也會產生與某種串行執行相同的效果

優點:

  • 提供最高級別的一致性保證,可以避免“髒讀”、“不可重複讀”和“幻讀”
  • 由於交易是完全序列化的,故可以避免如寫偏斜之類的複雜問題

缺點:

  • 並發性能可能會大大降低,特別是在有大量讀寫混合負載的系統中,因為需要對讀取和寫入的數據加鎖
  • 可能會導致資源競爭和交易阻塞,從而影響系統的整體性能

在 PostgreSQL 如何使用

BEGIN;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- do something

COMMIT;

上一篇
Day 18 PostgreSQL 資料庫備份和恢復
下一篇
Day 20: 實時監控和日誌
系列文
Hello SQL 初次見面你好30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言