MySQL 事務機制

事務處理是保證數據安全的重要機制,事務有四個重要屬性 ,根據它們的英文名稱能夠記爲ACID:html

  • 原子性(Atomic): 事務操做是不可分割的; 事務只存在已執行和未執行兩種狀態,不存在只執行了部分指令的狀況
  • 一致性(Consistency): 數據庫老是從一個一致的狀態轉換到另外一個一致狀態
  • 隔離性(Isolation): 同時執行的事務之間相互隔離,不會互相影響。
  • 持久性(Durability): 事務成功提交後, 其寫入的數據直到被覆蓋永久有效

咱們以銀行轉帳操做爲例理解事務:mysql

START TRANSACTION;
UPDATE account_balance SET balance = balance - 200.00 WHERE customer_id = 1;
UPDATE account_balance SET balance = balance + 200.00 WHERE customer_id = 2;
COMMIT;

上述事務執行先後數據庫只可能有兩種狀態: 帳戶一、2的餘額未變化, 帳戶1餘額減小200元帳戶2餘額增長200元。不可能存在帳戶1餘額減小而帳戶2餘額不變的狀態。sql

減小帳戶1餘額和增長帳戶2餘額是一個連續的過程, 不容許在事務執行過程當中對帳戶一、2餘額進行其它操做。數據庫

事務的原子性體如今兩方面:安全

  • 事務執行過程當中不容許插入其它操做。 減小帳戶1餘額和增長帳戶2餘額是一個連續的過程, 不容許在事務執行過程當中對帳戶一、2餘額進行其它操做。
  • 事務中的全部更改要麼都發生要麼都不發生, 不存在部分完成的狀況。 減小帳戶1餘額和增長帳戶2餘額要麼都發生,要麼都不發生

事務一致性體如今: 事務執行先後數據庫老是維持在一致狀態, 轉帳開始前到轉帳結束(不管轉帳成功或失敗)的整個過程當中, 帳戶一、2的總餘額始終不變。併發

事務隔離性體如今: 在轉帳事務減小帳戶1餘額後提交以前,另外一個事務查詢到的帳戶1餘額還是減小以前的。mvc

併發事務的潛在問題

  • 髒讀: 事務A修改了一個數據,但未提交,事務B讀到了事務A未提交的更新結果(即髒數據)。 如事務A在執行轉帳操做,從轉出帳戶扣除了餘額但未修改轉入帳戶餘額,此時事務B讀取了轉入帳戶的餘額, 即發生了髒讀。性能

  • 不可重複讀: 在同一個事務中,對於同一條數據兩次查詢讀到的結果不一致。好比,在事務A兩次查詢中間事務B修改了某條記錄,那麼事務A兩次查詢會讀到不一樣的結果。rest

  • 幻讀: 在同一個事務中,對於同一個查詢返回的記錄數不一致。形成這種現象的緣由是在事務A的兩次查詢中間事務B添加或刪除了記錄,致使事務A兩次查詢讀到不一樣的結果。code

幻讀和不可重複讀的區別在於,不可重複讀是對已存在記錄的修改致使的只須要對某一條記錄加鎖便可,幻讀增刪記錄致使的必須對全表加鎖。

事務隔離級別

MySQL提供四級事務隔離級別:

  • Read Uncommitted: 禁止多個事務同時修改同一條記錄,其它事務能夠讀取未提交的修改。 隔離級別最低,併發性能最高,會出現髒讀,不可重複讀和幻讀。

  • Read Committed: 禁止多個事務同時修改同一條記錄, 修改在提交前其它事務只能讀取修改前的版本。不會出現髒讀,但會出現不可重複讀和幻讀。

  • Repeated Read: 禁止多個事務同時修改同一條記錄, 事務提交前會鎖定全部讀取到的行,禁止其它事務修改它正在讀取的行。默認隔離級別,不會出現髒讀和不可重複讀,但會出現幻讀。

  • Serializable: 串行化執行,會鎖定全部涉及的數據表。能夠解決髒讀、不可重複讀和幻讀, 隔離級別最高,併發性能最低。

在實際應用中咱們須要根據須要選擇合適的事務隔離級別。

SET TRANSACTION語句能夠設置事務隔離級別:

-- SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ; -- 設置全部新鏈接的事務隔離級別
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; -- 設置當前鏈接的事務隔離級別
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- 設置下一個事務的隔離級別

事務併發控制原理

咱們一般有3種思路進行併發控制:

  • 悲觀鎖: 在事務進行過程當中數據老是處於被鎖定狀態。
    悲觀鎖對數據被其它事務修改的可能性持悲觀態度(傾向於可能發生), 經常使用於數據爭用激烈的情景。咱們一般使用的鎖便是悲觀鎖。

  • 樂觀鎖: 在事務執行過程當中數據不被鎖定, 在事務提交時會對是否發生數據爭用進行判斷,若未發生衝突則完成提交, 不然回滾事務。
    樂觀鎖認爲數據爭用發生的可能性較小, 經常使用於數據爭用比較少的情景。CAS原語是樂觀鎖的一個典型示例。

  • 快照: 全部對數據的修改都是在原有數據上產生了一個新的版本, 對數據的讀取是在快照(歷史版本)上進行的。寫操做產生新的版本不會影響在舊版本執行的讀操做。

MySQL默認使用的InnoDB存儲引擎使用悲觀鎖和快照(多版本併發控制, Multi Version Concurrent Control, MVCC)來實現事務的併發控制。

InnoDB採用兩階段鎖協議, 即事務分爲擴張階段和收縮階段, 擴張階段只容許加鎖不能釋放鎖, 收縮階段只能釋放鎖不能加鎖。

MVCC

InnoDB提供了全局惟一且有序的事務序列號, 以修改數據的事務序列號做爲數據版本號。併爲每條記錄維護兩個版本號: 最近修改版本號, 刪除事務號。

以 REPEATABLE READ 隔離級別下 MVCC 機制爲例:

  • SELECT: 被檢索的行必須同時知足兩個條件:

    • 行的修改版本號必須小於或等於當前事務序列號

    • 行的刪除版本號爲空或者大於當前事務序列號

    當前事務讀取到的數據老是事務開始前的版本或事務進行中修改的版本, 更晚開始的事務的修改不會被讀取。這種讀取方式稱爲快照讀。

  • INSERT: 將當前事務序列號做爲修改版本號
  • UPDATE: 插入一行新的記錄並使用當前事務序列號做爲修改版本號, 並將當前事務序列號做爲舊記錄的刪除事務號(標記爲已刪除)。
  • DELETE: 將當前事務序列號做爲記錄的刪除事務號(標記爲已刪除)。

由於快照讀不會讀取到更晚開始事務的修改, 所以不會產生不可重複讀和幻讀的問題。

在不一樣事務隔離級別下,快照讀的一致性是不一樣的:

  • READ COMMITTED: 每次SELECT時生成快照。SELECT 能夠看到其它已提交事務的修改
  • REPEATABLE READ: 事務開始時生成快照,事務內的更改會修改快照,SELECT 語句看不到其它事務的修改。

GAP 鎖

快照讀的缺陷在於只能讀取事務開始前的版本, 而對於修改操做而言必須讀取最新版本。

讀取最新版本的需求被InnoDB稱爲當前讀(Locking Read), 使用當前讀的語句包括 UPDATE, DELETE 和 SELECT ... IN SHARE MODE, SELECT ... FOR UPDATE。

InnoDB使用鎖來解決當前讀的問題, InnoDB 中存在三種行級鎖:

  • Record Lock: 單條行記錄上的鎖
  • Gap Lock:間隙鎖,鎖定一個範圍,但不包括記錄自己
  • Next-Key Lock: Record Lock + Next-Key Lock

用一個示例來講明GAP鎖:

1> START TRANSACTION;
1> DELETE FROM user WHERE age < 18;

在執行 DELETE 語句時 GAP LOCK 鎖定了全部 age < 18 的行。咱們在另外一個會話中開始另外一個事務, 此時事務1還沒有提交:

2> START TRANSACTION;
2> INSERT INTO user (age) VALUES (17);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

能夠看到事務2等待鎖超時, 在事務1釋放 GAP LOCK 以前不能插入 age < 18 的行, 原有的 age < 18 的行也沒法修改。

InnoDB 鎖定索引而非鎖定數據行, BTREE索引是有序的。GAP LOCK 鎖定了索引樹中 age < 18 的空間(即索引間的空隙), 被鎖定的區間不能插入記錄也不能修改已有記錄。

在 REPEATABLE READ 隔離級別下不出現幻讀是 InnoDB 存儲引擎的特性不是 MySQL 的要求, 在使用其它存儲引擎時仍可能出現幻讀問題。

更多關於讀一致性的內容能夠參考 InnoDB 官方文檔: innodb-consistent-read

相關文章
相關標籤/搜索