1. 程式人生 > >資料庫的事務和併發問題

資料庫的事務和併發問題

資料庫事務

事務(Transaction)是併發控制的基本單位。所謂的事務,它是一個操作序列,這些操作要麼全部執行,要麼全部都不執行。比如,銀行轉賬,從一個賬號扣錢,然後另一個賬號餘額增加,這兩個操作要麼都執行,要麼都不執行。這兩個操作組合在一起就是事務。

資料庫事務有嚴格的定義,它必須同時滿足4個特性:

  1. 原子性,Atomic
  2. 一致性,Consistency
  3. 隔離性, Isolation
  4. 永續性,Durabiliy

簡稱ACDI。下面是對每一個特性的說明:

  • 原子性:表示組成一個事務的多個數據庫操作是一個密不可分的原子單元,只有所有的操作執行成功,整個事務才提交。事務中的任何一個數據庫操作失敗,已經執行的任何操作都必須撤銷(回滾),讓資料庫恢復到事務提交之前的狀態。
  • 一致性:資料庫總是從一個一致性狀態裝換到另一個一致性狀態。一致性狀態的含義是資料庫中的資料應該滿足資料庫約束。
  • 隔離性:在併發資料操作時,不同的事務擁有各自的資料空間,它們的操作不會對對方產生干擾。但是也並非要做到完全無干擾。資料庫規定了多個隔離級別,不同的隔離級別的干擾程度是不同,隔離級別越高,資料一致性越好,但併發性越弱。
  • 永續性:一旦資料庫提交之後,事務中的所有操作都必須被持久化都資料庫中。即使在提交事務後,資料庫重啟時,也必須保證能夠通過某種機制恢復資料。

在這些事務的特徵中,資料”一致性“是最終目標,其他特性都是為達到這個目標而採取的措施。

資料庫併發的問題

一個數據庫可能會有多個客戶端同時訪問,資料庫中相同的資料就有可能同時被多個事務訪問,如果沒有采取必要的隔離措施,就會導致各種問題,破壞資料的完整性,這些問題可以分為5中,兩類:

  1. 資料讀取的問題:
    • 髒讀
    • 不可重複讀
    • 幻想讀
  2. 資料更新問題
    • 第一類丟失更新
    • 第二類丟失更新

1. 髒讀(direct read)

A事務讀取B事務尚未提交更改的資料,並在這個資料的基礎上進行操作。如果恰巧B事務回滾,那麼A事務讀取到的資料是不被承認的。通過一個取款事務和轉賬事務來說明這個問題。

時間 轉賬事務A 取款事務B
T1 開始事務
T2 開始事務 查詢賬戶餘額1000元
T3 取出500元,把餘額改為500元
T4 查詢餘額500元(髒讀)
T5 撤銷事務,餘額恢復為1000元
T6 匯入100元,把餘額改為600元
T8 提交事務

在這個場景中轉賬事務A讀取到取款事務B中的未提交的資料,導致髒讀。

2. 不可重複讀(unrepeatable read)

不可重複讀是指A事務讀取B事務已經提交更改的資料。假設A在取款事務的過程中,B往該賬戶轉賬100元,A兩次讀取賬戶的餘額不一致。

時間 取款事務A 轉賬事務B
T1 開始事務
T2 開始事務
T3 查詢賬戶餘額為1000元
T4 查詢賬戶餘額1000元
T5 取出100元,把餘額改為900元
T6 查詢事務
T7 查詢賬戶餘額為900元

在同一事務中,T4和T7時間點讀取的賬戶餘額不一致。

3. 幻想讀(phantom read)

A事務讀取B事務提交的新增資料,這時A事務將出現幻想讀現象。幻想讀一般發生在計算統計資料的事務中。

舉個例子,比如在銀行系統的同一個事務中有兩次統計存款使用者的總金額,在兩次統計中剛好新增了一個存款,這時,兩次統計的結構將會不一致。

時間 統計金額事務A 轉賬事務B
T1 開始事務
T2 開始事務
T3 統計總存款為1000元
T4 新增一個存款賬戶,存款100元
55 提交事務
T6 再次統計總存款數為10100元(幻想讀)

如果新增的資料剛好滿足事務的查詢條件,那麼這個新資料就會出現事務的視野中,因而產生了兩次結構不一致。

幻想讀和不可重複讀這兩個概念比較容易混淆 ,幻想讀是指讀到了其他已經提交的事務的新增資料,而不可重複讀是指讀到了已經提交的更改資料(更改或者刪除)。

為了避免這兩種情況,採取的對策是不同的:防止讀到更新的資料,只需要對操作的資料新增行級鎖,阻止操作中的資料發生改變;而防止讀到新增的資料,則往往需要新增表級鎖——將整張表加鎖,防止新增資料。

4. 第一類丟失更新

第一類丟失更新是一個事務撤銷時把另一個事務的資料覆蓋了。下面通過一個轉賬的例子來看一下這個問題。

時間 取款事務A 轉賬事務B
T1 開始事務
T2 開始事務
T3 查詢餘額為1000元
T4 查詢餘額為1000元
T5 匯入100元,把餘額修改為1100元
T6 提交事務
T7 取出100元,把餘額修改為900元
T8 撤銷修改
T9 把餘額恢復為1000元(丟失更新)

5. 第二類丟失更新

一個事務覆蓋另一個事務已經提交的資料。造成另一個事務所做的操作丟失。

時間 取款事務A 轉賬事務B
T1 開始事務
T2 開始事務
T3 查詢賬戶餘額為1000元
T4 查詢賬戶餘額為1000元
T5 取出100元,把餘額修改為900元
T6 提交事務
T7 匯入100元
T8 提交事務
T9 把餘額修改為1100元(丟失更新)

總結:第一類為撤銷時覆蓋,第二類為提交時覆蓋。

資料庫鎖機制

資料庫的併發會引起很多問題,當然有些問題還可以容忍,但是有的問題卻是致命的。併發問題一般都會用鎖解決,在資料庫中也是用鎖解決的,但是不同的資料庫對於鎖的實現是不同的,但基本的原理是相同。

按鎖定的物件可以分為:

  • 表鎖定:對於整張表鎖定
  • 行鎖定:對於表中的特定行鎖定

從併發的資料關係中又可以分為

  • 獨佔鎖:共享鎖會防止獨佔鎖,但允許其他共享鎖的訪問。
  • 共享鎖:獨佔鎖獨自佔領表或行,防止其他共享鎖的訪問,當然也訪問其他獨佔鎖。

在資料更新的時候,資料庫必須在進行更改的行上施加行獨佔鎖,也就是說INSERT,UPDATE,DELETE等語句都會隱式採用必要的行鎖定。

事務的隔離級別

儘管資料庫為使用者提供了鎖的DML操作方式,但是直接使用還是很麻煩的,因此資料庫為使用者提供了自動鎖的機制。也就是隔離級別,只要使用者指定的回話的隔離級別,資料庫就會分析SQL語句,然後進行合適的加鎖,當資料鎖的資料太多的時候,自動進行鎖升級來提高系統的,效能,這一過程對使用者是透明的(不可見)的。

SQL標準定義了4個事務級別,每一個級別都規定了一個事務中所做的修改,哪些在事務中是可見的,哪些是不可見的。較低的隔離通常可以執行更高的併發,系統開銷也更低。

下面的是四中資料庫事務的介紹:

  1. READ UNCOMMITED(未提交讀) 事務中的修改,即使沒有提交對其它事務都是可見的。事務可以讀取未提交的資料,這也被稱為髒讀。一般很少使用。
  2. READ COMMITED(提交讀) 大多數的資料庫的預設隔離級別都是READ COMMITED。READ_COMMITED從一個事務開始時,只能”看見“已經提交的修改。也就是說:一個事務從開始到提交前,所做的任何修改對其他事務是不可見的。這個級別有時候也叫做不可重複讀,因為兩次執行查詢可能會得到不同的結果。
  3. REPEATABLE READ(可重複讀) REPEATABLE READ解決了髒讀的問題,該級別保證在同一個事務中多次讀取同樣的記錄的結果是一致的。但是這個級別還是沒有解決另一個問題:幻讀。
  4. SERIALIZABLE(可序列化) SERIALIZABLE是最高的隔離級別。它通過事務串執行,避免了前面所說的幻讀問題。簡單來說SERIALIZABLE會為每一行資料都加鎖,所以會導致大量的鎖超時和競爭。實際中很少使用這個隔離級別。

下表為事務隔離級別對併發問題的解決情況:

隔離級別 髒讀 不可重複讀 幻想讀 第一類丟失更新 第二類丟失更新
READ UNCOMMITED 允許 允許 允許 不允許 允許
READ COMMITED 不允許 允許 允許 不允許 允許
REPEATABLE READ 不允許 不允許 允許 不允許 不允許
SERIALIZABLE 不允許 不允許 不允許 不允許 不允許

其中READ UNCOMMITED併發性和吞吐量最好,SERIALIZABLE的最差,所以事務的隔離級別和資料庫的併發行是對立的。