文檔版本: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 SHARE
或 SELECT FOR UPDATE
),UPDATE
語句和DELETE
語句,加鎖狀況取決於語句是使用了惟一查詢(使用了惟一索引),仍是使用了範圍條件查詢。數據庫
在這個級別下,每一次快照讀,甚至在同一事務內,都會設置一次新快照來讀取。對於快照讀,詳見Section 15.7.2.3, 「Consistent Nonlocking Reads」。
對於加鎖讀(SELECT FOR SHARE
或 SELECT 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
。)