1. 程式人生 > >Mysql 事務與MVCC併發控制

Mysql 事務與MVCC併發控制

本文簡單介紹了事務的ACID特性和Mysql事務的四種隔離級別,以及MVCC併發控制手段。

事務的ACID特性

  1. 原子性(Atomicity)
    原子性強調事務作為一個整體,要麼完全執行成功,要麼完全執行失敗,不能存在一些操作成功,而其他的操作失敗這樣的可能會破壞資料一致性的情況。
  2. 一致性(Consistency)
    一致性指的是資料永遠在一致的狀態,而不會存在一部分資料在一個一致的狀態,另一部分資料在另一個一致的狀態,從而導致總的資料不在一致的狀態。一致的狀態這個說法需要根據具體的業務的資料的一致性要求來確定,如圖書借閱系統中,一本書只能被被一個讀者借閱,若這本書的狀態為同時被兩個人借閱,則產生了資料不一致的情況。
  3. 隔離性(Isolation)
    隔離性指的是不同的事務獨立執行,相互之間互不干擾。即一個事務完成、提交併將對資料的修改同步到資料庫之前,這個事務的操作對於其它的事務是不可見的。
  4. 永續性(durability)
    永續性指事務一旦執行成功並提交了,那麼事務對資料的修改將會一直儲存,不會因為故障而丟失。

事務的四個隔離級別

  1. 未提交讀
    這個級別事務可以讀取到另外的事務尚未提交的修改,這個隔離級別效能稍好,但是會產生髒讀的問題。一般不建議使用。
  2. 讀提交
    這個級別事務只能看到自己已經修改的資料和已經提交的事務的狀態,但是這個級別會產生不可重複讀這樣的問題,即在事務執行期間有的資料被修改了,則在修改資料的事務提交前後的兩次讀取會讀取到不同的值。
  3. 可重複讀
    可重複讀保證了在同一個事務中對同一個資料的多次讀取值相同(本事務沒修改)。可重複讀存在的問題是幻讀。幻讀是一個更大粒度的問題,即在一次事務執行期間,事務處理的資料範圍插入或刪除了資料,和不可重複讀的區別是一個是單條資料,一個是一個範圍的資料集合。InnoDB通過MVCC解決了幻讀的問題。
  4. 可序列化
    可序列化強制事務序列執行,避免了事務交叉執行的若干問題。可序列化的實現會在讀取的每一行資料上都加鎖,確保兩個有資料競爭的事務依次執行。

MVCC(多版本併發控制)

MVCC 通過對資料新增基於時間戳的版本標識來避免加鎖,達到在提高效能的同時保證併發條件下資料一致性的目標。每個資料庫儲存引擎對MVCC的實現不同,這裡介紹一下InnoDB的實現方式。InnoDB的MVVC按照事務開始的先後順序為每個事務分配一個編號。InnoDB通過在資料庫的每一行後面加上兩個隱藏的列來記錄建立這條資料的事務編號和刪除這條資料的版本編號來進行併發控制。需要注意的是,UPDATE操作會標識這條資料已經過期,同時插入一條新的資料,並以當前事務編號作為建立時間。接下來看看在可重複讀的事務隔離級別下,MVCC的具體行為:
SELECT:
select 操作只能讀取到在本次事務開始之前存在於資料庫中的資料或本次事務建立的資料。如何實現呢,就是檢查每個資料行的建立時間和刪除時間。本次事務開始之前存在於資料庫中表示:建立時間小於本次事務編號且刪除時間未定義或大於本次事務編號。本次事務建立的且未刪除的表示:建立時間為本次事務編號且刪除時間未定義。
INSERT:
插入一條資料,使用當前的事務編號作為建立時間
DELETE:
將當前事務編號儲存到資料庫作為資料行的刪除時間
UPDATE:
插入一條新的資料,將當前事務編號作為資料行的建立時間,並將當前事務編號作為資料行 的刪除時間
考慮存在的一個問題,假設一個銀行系統有Account表有三個賬戶,A,B,C,版本如下

賬戶名稱 餘額 建立時間 刪除時間
A 300 1 未定義
B 0 1 未定義
C 0 1 未定義

接下來兩個事務
T2: 某些耗時操作+從賬戶A轉賬200到賬戶B
some op
balance_a = select 餘額 from account where 賬戶名稱 = A
balance_b = select 餘額 from account where 賬戶名稱 = B
assert balance_a >= 200
update account set 餘額 = balance_b + 200 where 賬戶名稱 = B
update account set 餘額 = balance_a -200 where 賬戶名稱 = A
T3: 從賬戶A轉賬200到賬戶C
balance_a = select 餘額 from account where 賬戶名稱 = A
balance_c = select 餘額 from account where 賬戶名稱 = C
assert balance_a >= 200
update account set 餘額 = balance_c + 200 where 賬戶名稱 = C
update account set 餘額 = balance_a -200 where 賬戶名稱 = A

假設由於T2在執行耗時操作,T3先全部執行了,那麼資料就是這樣

賬戶名稱 餘額 建立時間 刪除時間
A 300 1 3
A 100 3 未定義
B 0 1 未定義
C 0 1 3
C 200 3 未定義

那麼T2選擇到的balance_a 將會是 300,T2執行完成之後,資料庫如下

賬戶名稱 餘額 建立時間 刪除時間
A 300 1 2
A 100 3 未定義
A 100 2 未定義
B 0 1 2
B 200 2 未定義
C 0 1 3
C 200 3 未定義

尷尬的事情發生了,A賬號只有300元,卻成功向外轉出了400元,這是為什麼呢,還需要再探究一下
、-------------------
分割線
經過實驗,最終理解了原因。其實上面的操作沒有問題,上面的操作是在可重複讀這個隔離級別限定的,這個隔離級別並沒有說完全可以保證資料一致性,只是說明了不會存在髒讀,以及不可重複讀這兩個問題,至於資料一致性的問題,需要程式設計師自己來維護。很顯然,像上面這樣的操作,需要通過對資料加鎖來保證資料的一致性。