MySQL InnoDB Isolation Level 隔離層級是什麼?

在解釋 Isolation Level 隔離層級之前,先說明三個名詞 : 髒讀 (dirty read)、幻讀 (phantom read)、不可重複讀 (Non-Repeatable Read)。

髒讀 (dirty read) : 指在一個交易讀取到另一個尚未提交 (un-commit) 的交易資料。如果那個交易最終被回滾(Rollback),則讀到的資料就是不準確的,這就是所謂的「髒讀」。

髒讀就像做夢撿到錢,結果醒來什麼都沒有。

幻讀 (phantom read) : 指在一個交易中,當它多次執行同一查詢時,由於其他交易插入、更新或刪除了符合查詢條件的行,導致每次查詢返回的結果不同。

幻讀就像見到鬼,資料多了或是少了。

不可重複讀 (Non-Repeatable Read) : 指在一個交易在多次讀取同一筆資料時,該資料的值因為另一個交易的更新而發生變化。

不可重複讀就像碰到魔術師,明明手上是棍子,等一下變成鴿子。

髒讀、幻讀、不可重複讀都不是正常的現象,除非有特別的目的,不然都應該盡量避免。


隔離層級是什麼?

隔離層級(Isolation Level)是資料庫管理系統中設定交易(Transaction)之間互相隔離程度的配置選項。它控制了在多個交易並發 (concurrent) 執行時,數據的一致性和完整性,防止數據競爭和不一致的問題。隔離層級越高,交易之間的干擾越小,但可能會影響系統的性能和並發性。

InnoDB是MySQL中唯一全面支持四個隔離層級的儲存引擎,InnoDB有四個隔離層級 (Isolation Level) : 

語法

SET [SESSION / GLOBAL] TRANSACTION ISOLATION LEVEL 
[READ UNCOMMITTED / READ COMMITTED / REPEATABLE READ / SERIALIZABLE];


(1) 讀未提交(Read Uncommitted):
在這個隔離層級下,交易可以讀取尚未提交的修改,也就是說,一個交易可以「看到」其他未提交交易所做的變更。這個層級可能會導致「髒讀」(Dirty Read)。 

這在某些需要極高效能且可以容忍數據一致性問題的情境下可能會有用,例如在某些類型的分析或報告中。然而,一般建議在大多數應用中使用更嚴格的隔離層級,以避免髒讀等問題。

當效能遠大於資料正確時,可能會使用這個隔離層級。

特性:交易可以讀取其他交易尚未提交的數據。
避免的問題:無。
典型問題:髒讀(Dirty Reads)。
適用情境:很少用,因為容易出現數據不一致問題。

範例 :  t1 是InnoDB的表單,欄位f1 (INT、pk)、f2 (INT)。

連線#1
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN;

連線#2
BEGIN;
UPDATE t1 SET f2=1 WHERE f1=1;

連線#1
SELECT * FROM t1;
會看到連線#2的更新結果 (f2=1)

連線#2
ROLLBACK;
結果回滾,實際上沒有執行資料更新

連線#1
SELECT * FROM t1;
這次看到的是不同的結果 (f2是更新前的舊資料)

如下圖


(2) 讀已提交(Read Committed):
在這個隔離層級下,一個交易只能看到其他交易已經提交的修改。這可以防止髒讀,但仍然可能遇到「不可重複讀」(Non-repeatable Read)的問題。 

這個隔離層級可以避免交易看到未確認的數據變更,從而提高資料的一致性。然而,它仍然可能遇到「不可重複讀」的問題,因為在同一交易中的不同查詢之間,另一個已經提交的交易可能會更改數據。

特性:交易只能讀取其他交易已提交的數據。 避免的問題:髒讀。 典型問題:不可重複讀(Non-repeatable Reads)。 適用情境:常用於許多應用,提供合理的一致性和性能平衡。

範例 :  t1 是InnoDB的表單,欄位f1 (INT、pk)、f2 (INT)。

連線#1
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;

連線#2
BEGIN;
UPDATE t1 SET f2=1 WHERE f1=1;
COMMIT;

連線#1
SELECT * FROM t1;
會看到連線#2的更新結果 (f2=1)

連線#2
BEGIN;
UPDATE t1 SET f2=2 WHERE f1=1;
COMMIT;

連線#1
SELECT * FROM t1;
這次看到的是不同的結果 (f2=2)

如下圖



(3) 可重複讀(Repeatable Read):
這是 MySQL 的預設隔離層級。在這個層級下,交易在整個過程中可以看到一個一致性的快照,即使其他交易提交了新的變更,這些變更也不會被當前交易所見。這個層級可以防止髒讀和不可重複讀,但仍可能遇到「幻讀」(Phantom Read)。 

在這個隔離等級下,如果其他交易對資料進行了修改(例如更新或刪除了某些紀錄),這些改變將不會在你的交易中被看到,直到你的交易提交(Commit)之後。這樣可以避免在交易執行期間出現「不可重複讀」(Non-repeatable Reads)的問題。

特性:在同一交易中多次讀取相同資料集時,結果是一致的。
避免的問題:髒讀和不可重複讀。
典型問題:幻讀(Phantom Reads)。
適用情境:MySQL 預設隔離層級,適用於需要一致性較高的應用。

範例 :  t1 是InnoDB的表單,欄位f1 (INT、pk)、f2 (INT)。

連線#1
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;

連線#2
BEGIN;
UPDATE t1 SET f2=1 WHERE f1=1;
COMMIT;

連線#1
SELECT * FROM t1;
會看到連線#2的更新結果 (f2=1)

連線#2
BEGIN;
UPDATE t1 SET f2=2 WHERE f1=1;
COMMIT;

連線#1
SELECT * FROM t1;
這次看到的是相同的結果 (f2=1)

如下圖



(4) 序列化(Serializable):
這是最高的隔離層級,在這個層級下,交易將完全依序進行,避免了幻讀的發生。這個層級通常會通過鎖定資料來實現,因此可能會對效能有所影響。

在這個隔離級別下,每個交易的讀和寫操作會互相阻塞,確保即便是同步執行的交易,也不會相互影響,從而達到順序執行的效果。這可以避免任何形式的讀寫不一致,但會降低並發性,可能影響系統性能。

特性:最高隔離級別,確保交易完全隔離,彷彿是順序執行。
避免的問題:髒讀、不可重複讀和幻讀。
典型問題:性能降低,並發性低。 
適用情境:需要最高數據一致性的情況,例如金融交易。

範例 :  t1 是InnoDB的表單,欄位f1 (INT、pk)、f2 (INT)。

連線#1
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
INSERT INTO t1 (f1,f2) VALUES (4,4);

連線#2
UPDATE t1 SET f2=1 WHERE f1=4;
會進入等待中

連線#1
COMMIT;

連線#2
才會執行

如下圖


※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※


至於如何知道目前的隔離級別呢? 可以使用以下指令 : 

-- 查詢當前會話的隔離級別
SELECT @@SESSION.tx_isolation; 

-- 查詢全域的隔離級別
SELECT @@GLOBAL.tx_isolation;


假設有一個情境,你需要處理一個財務報表生成的交易,這個過程需要最高的一致性,以避免任何數據不一致或競爭條件。在這種情況下,你可以將隔離級別調整為「序列化」(Serializable)。以下是修改隔離級別並執行相關操作的範例:

(1) 因此先設定會話隔離級別為序列化
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;

(2) 開始交易 START TRANSACTION;

(3) 查詢所有交易的總金額
SELECT SUM(amount) AS total_amount
FROM transactions
WHERE transaction_date BETWEEN '2024-01-01' AND '2024-12-31';
(以上語法只是範例)

(4) 提交交易 COMMIT;

這樣算出來的交易總額才會比較正確。

以比較的方式,把四種隔離層級呈現如下表 : 







這樣就可以知道,未提交讀 = 效能極大化+資料正確最小化,序列化 = 資料正確極大化+效能最小化。而可重複讀可以避免髒讀及不可重複讀,因此是MySQL的預設。

張貼留言

0 留言