事務隔離級別 - MySQL 8.0官方文檔筆記(二)

文檔版本:8.0
來源:transaction isolation levels
上一篇:InnoDB中的鎖html

本篇主要介紹InnoDB的事務隔離級別。mysql

事務隔離級別

事務隔離是數據庫發展的基礎之一。隔離性(Isolation)是ACID中的I;不一樣的隔離級別用於在性能和多事務並行查詢時的可靠性、一致性、再現性之間微調。
InnoDB完整實現了SQL:1992標準中描述的四個隔離級別: 讀未提交,讀已提交,可重複讀,序列化。InnoDB的默認級別是可重複讀。
用戶能夠經過SET TRANSACTION語句設置本會話或後續全部鏈接使用的隔離級別。若是要設置服務器針對全部鏈接的默認級別,在命令行或配置文件中使用--transaction-isolation選項。關於隔離級別和設置級別的語法可在 Section 13.3.7, 「SET TRANSACTION Statement」 中詳細瞭解。
針對在此描述的這些隔離級別,InnoDB支持對它們使用不一樣的加鎖策略。例如,對於一些重要數據,須要嚴格遵循ACID,那麼可使用默認級別可重複讀以確保更高的一致性;相反地,對於批量報告數據,精確的一致性與可重放的結果顯然沒有最小化鎖開銷重要,那麼可使用讀已提交,甚至是讀未提交來放寬一致性規則。序列化比可重複讀更加嚴格,通常用於特殊場景,例如XA事務,或者排查併發與死鎖問題。
下面列舉MySQL是如何支持不一樣的隔離級別的。按照使用頻率從高到低依次排序。sql

可重複讀

可重複讀是InnoDB的默認隔離級別。在這個級別下,同一事務內的快照讀會使用第一次讀取時生成的快照。這意味着若是你在同一事務內執行多條普通(無鎖)SELECT語句,這些語句的結果是一致的。詳見Section 15.7.2.3, 「Consistent Nonlocking Reads」
對於加鎖讀(SELECT FOR SHARESELECT FOR UPDATE),UPDATE語句和DELETE語句,加鎖狀況取決於語句是使用了惟一查詢(使用了惟一索引),仍是使用了範圍條件查詢。數據庫

  • 對於惟一查詢,InnoDB只會鎖住命中的索引值,不會鎖住以前的間隙。
  • 對於其餘查詢,InnoDB鎖住掃描到的索引,使用間隙鎖或臨鍵鎖阻塞其餘事務在間隙內的插入操做。對於間隙鎖和臨鍵鎖,詳見Section 15.7.1, 「InnoDB Locking」。(也能夠看個人翻譯筆記:InnoDB中的鎖)

讀已提交

在這個級別下,每一次快照讀,甚至在同一事務內,都會設置一次新快照來讀取。對於快照讀,詳見Section 15.7.2.3, 「Consistent Nonlocking Reads」
對於加鎖讀(SELECT FOR SHARESELECT FOR UPDATE),UPDATE語句和DELETE語句,InnoDB只鎖定索引值,不會鎖住以前的間隙,所以其餘事務能夠在索引值旁進行插入。間隙鎖只會用於外鍵約束檢查和重複鍵檢查。
由於間隙鎖被禁用,其餘會話能夠在間隙中插入新行,因此可能會發生幻行。對於幻行,詳見Section 15.7.4, 「Phantom Rows」
讀已提交級別只支持數據行binlog。若是設置爲混合模式,MySQL服務器會自動使用數據行binlog。
使用讀已提交還會有如下影響:服務器

  • 對於UPDATE語句和DELETE語句,InnoDB只會鎖住將要更新或刪除的行。在MySQL計算出WHERE條件後,不匹配行的行鎖會被釋放。這使得死鎖發生的機率大大下降,但仍沒法杜絕。
  • 對於UPDATE語句,若是行已經被鎖住,InnoDB會執行一個「半快照讀」,返回最後提交的版本給MySQL從而決定WHERE條件將匹配哪些行。若是有匹配行(將必須被更新),MySQL再次讀行而且此次InnoDB將會加鎖或等待鎖。

下面舉一個例子,先創建一張表:併發

CREATE TABLE t (a INT NOT NULL, b INT) ENGINE = InnoDB;
INSERT INTO t VALUES (1,2),(2,3),(3,2),(4,3),(5,2);
COMMIT;

表沒有索引,因此搜索和索引掃描將使用隱藏的聚簇索引,而不是索引列,來鎖定行記錄(詳見Section 15.6.2.1, 「Clustered and Secondary Indexes」)。
讓一個會話用下列語句執行UPDATE:性能

# Session A
START TRANSACTION;
UPDATE t SET b = 5 WHERE b = 3;

接着讓一個會話用下列語句執行UPDATE:命令行

# Session B
UPDATE t SET b = 4 WHERE b = 2;

InnoDB執行各個UPDATE時,首先會得到各行的獨佔鎖,而後決定是否修改行。若是InnoDB沒有修改行,就釋放鎖;不然InnoDB在事務結束前會一直持有鎖,因而事務流程會被這樣影響:
當使用默認的可重複讀隔離級別時,第一個UPDATE隨着全表掃描獲取每行的獨佔鎖並不會釋放:翻譯

x-lock(1,2); retain x-lock
x-lock(2,3); update(2,3) to (2,5); retain x-lock
x-lock(3,2); retain x-lock
x-lock(4,3); update(4,3) to (4,5); retain x-lock
x-lock(5,2); retain x-lock

第二個UPDATE一旦嘗試獲取任何鎖就會阻塞(由於第一個UPDATE已經獲取了全部行的鎖),直到第一個UPDATE提交或回滾:code

x-lock(1,2); block and wait for first UPDATE to commit or roll back

但若是使用讀已提交,第一個UPDATE獲取每一行的獨佔鎖,而後釋放不須要修改的:

x-lock(1,2); unlock(1,2)
x-lock(2,3); update(2,3) to (2,5); retain x-lock
x-lock(3,2); unlock(3,2)
x-lock(4,3); update(4,3) to (4,5); retain x-lock
x-lock(5,2); unlock(5,2)

對於第二個UPDATE,InnoDB執行一個「半快照讀」,返回所讀行最後提交的版本給MySQL,從而決定WHERE條件將匹配哪些行。

x-lock(1,2); update(1,2) to (1,4); retain x-lock
x-lock(2,3); unlock(2,3)
x-lock(3,2); update(3,2) to (3,4); retain x-lock
x-lock(4,3); unlock(4,3)
x-lock(5,2); update(5,2) to (5,4); retain x-lock

不過,若是WHERE條件包含索引列,而且InnoDB使用了這個索引,那麼只會針對索引列進行獲取、保持鎖。在下面的例子中,第一個UPDATE會獲取全部b=2的行的獨佔鎖。第二個UPDATE在試圖獲取同一條記錄的獨佔鎖時被阻塞,而且也會使用b列索引。

CREATE TABLE t (a INT NOT NULL, b INT, c INT, INDEX (b)) ENGINE = InnoDB;
INSERT INTO t VALUES (1,2,3),(2,2,4);
COMMIT;

# Session A
START TRANSACTION;
UPDATE t SET b = 3 WHERE b = 2 AND c = 3;

# Session B
UPDATE t SET b = 4 WHERE b = 2 AND c = 4;

讀已提交隔離級別能夠在MySQL啓動時和運行中設置,若是在運行中設置,能夠設置爲全局生效,或單個會話生效。

讀未提交

SELECT語句將以無鎖方式執行,但可能會使用行的舊版本數據。所以在這個級別下,讀語句可能不一致,這也稱做髒讀。在其餘方面,讀未提交與讀已提交表現相同。

序列化

這個級別與可重複讀相似,但若是autocommit被禁用,InnoDB會隱式地將全部普通SELECT語句轉換爲SELECT ... FOR SHARE。若是autocommit被開啓,SELECT語句自成一條事務。所以它是隻讀的,而且在快照(無鎖)讀時可以被序列化,也不會被其餘事務阻塞。(若是要使普通SELECT語句在其它事務修改了行後阻塞,禁用autocommit。)

相關文章
相關標籤/搜索