天天看點

資料庫,事務,髒資料

資料庫的事務,是指作為單個邏輯工作單元執行的一系列操作。 事務處理可以確定除非事務性單元内的所有操作都成功完成,否則不會永久更新面向資料的資源。通過将一組相關操作組合為一個要麼全部成功要麼全部失敗的單元,可以簡化錯誤恢複并使應用程式更加可靠。一個邏輯工作單元要成為事務,必須滿足所謂的ACID(原子性、一緻性、隔離性和持久性)屬性。

操作流程

  設想網上購物的一次交易,其付款過程至少包括以下幾步資料庫操作:

  · 更新客戶所購商品的庫存資訊

  · 儲存客戶付款資訊--可能包括與銀行系統的互動

  · 生成訂單并且儲存到資料庫中

  · 更新使用者相關資訊,例如購物數量等等

  正常的情況下,這些操作将順利進行,最終交易成功,與交易相關的所有資料庫資訊也成功地更新。但是,如果在這一系列過程中任何一個環節出了差錯,例如在更新商品庫存資訊時發生異常、該顧客銀行帳戶存款不足等,都将導緻交易失敗。一旦交易失敗,資料庫中所有資訊都必須保持交易前的狀态不變,比如最後一步更新使用者資訊時失敗而導緻交易失敗,那麼必須保證這筆失敗的交易不影響資料庫的狀态--庫存資訊沒有被更新、使用者也沒有付款,訂單也沒有生成。否則,資料庫的資訊将會一片混亂而不可預測。

  資料庫事務正是用來保證這種情況下交易的平穩性和可預測性的技術。

資料庫事務的ACID屬性

原子性(atomic)

  事務必須是原子工作單元;對于其資料修改,要麼全都執行,要麼全都不執行。通常,與某個事務關聯的操作具有共同的目标,并且是互相依賴的。如果系統隻執行這些操作的一個子集,則可能會破壞事務的總體目标。原子性消除了系統處理操作子集的可能性。

一緻性(consistent)

  事務在完成時,必須使所有的資料都保持一緻狀态。在相關資料庫中,所有規則都必須應用于事務的修改,以保持所有資料的完整性。事務結束時,所有的内部資料結構(如 B 樹索引或雙向連結清單)都必須是正确的。某些維護一緻性的責任由應用程式開發人員承擔,他們必須確定應用程式已強制所有已知的完整性限制。例如,當開發用于轉帳的應用程式時,應避免在轉帳過程中任意移動小數點。

隔離性(insulation)

  由并發事務所作的修改必須與任何其它并發事務所作的修改隔離。事務檢視資料時資料所處的狀态,要麼是另一并發事務修改它之前的狀态,要麼是另一事務修改它之後的狀态,事務不會檢視中間狀态的資料。這稱為可串行性,因為它能夠重新裝載起始資料,并且重播一系列事務,以使資料結束時的狀态與原始事務執行的狀态相同。當事務可序列化時将獲得最高的隔離級别。在此級别上,從一組可并行執行的事務獲得的結果與通過連續運作每個事務所獲得的結果相同。由于高度隔離會限制可并行執行的事務數,是以一些應用程式降低隔離級别以換取更大的吞吐量。防止資料丢失

持久性(durability)

  事務完成之後,它對于系統的影響是永久性的。該修改即使出現緻命的系統故障也将一直保持。

 一個資料庫可能擁有多個通路用戶端,這些用戶端都可以并發方式通路資料庫。資料庫中的相同資料可能同時被多個事務通路,如果沒有采取必要的隔離措施,就會導緻各種并發問題,破壞資料的完整性。這些問題可以歸結為5類,包括3類資料讀問題(髒讀、幻象讀和不可重複讀)以及2類資料更新問題(第一類丢失更新和第二類丢失更新)。下面,我們分别通過執行個體講解引發問題的場景。

髒讀(dirty read) 

    在講解髒讀前,我們先講一個笑話:一個有結巴的人在飲料店櫃台前轉悠,老闆很熱情地迎上來:“喝一瓶?”,結巴連忙說:“我…喝…喝…”,老闆麻利地打開易拉罐遞給結巴,結巴終于憋出了他的那句話:“我…喝…喝…喝不起啊!”。在這個笑話中,飲料店老闆就對結巴進行了髒讀。 

A事務讀取B事務尚未送出的更改資料,并在這個資料的基礎上操作。如果恰巧B事務復原,那麼A事務讀到的資料根本是不被承認的。來看取款事務和轉賬事務并發時引發的髒讀場景:

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

  在這個場景中,B希望取款500元而後又撤銷了動作,而A往相同的賬戶中轉賬100元,就因為A事務讀取了B事務尚未送出的資料,因而造成賬戶白白丢失了500元。

不可重複讀(unrepeatable read) 

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

時間 取款事務A 轉賬事務B
T1 開始事務
T2 開始事務
T3 查詢賬戶餘額為1000元      
T4 查詢賬戶餘額為1000元
T5 取出100元把餘額改為900元
T6 送出事務                  
T7 查詢賬戶餘額為900元(和T4讀取的不一緻)

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

幻象讀(phantom read) 

    A事務讀取B事務送出的新增資料,這時A事務将出現幻象讀的問題。幻象讀一般發生在計算統計資料的事務中,舉一個例子,假設銀行系統在同一個事務中,兩次統計存款賬戶的總金額,在兩次統計過程中,剛好新增了一個存款賬戶,并存入100元,這時,兩次統計的總金額将不一緻:  

時間 統計金額事務A 轉賬事務B
T1 開始事務
T2 開始事務
T3 統計總存款數為10000元
T4 新增一個存款賬戶,存款為100元
T5 送出事務     
T6 再次統計總存款數為10100元(幻象讀)

  如果新增資料剛好滿足事務的查詢條件,這個新資料就進入了事務的視野,因而産生了兩個統計不一緻的情況。 

  幻象讀和不可重複讀是兩個容易混淆的概念,前者是指讀到了其它已經送出事務的新增資料,而後者是指讀到了已經送出事務的更改資料(更改或删除),為了避免這兩種情況,采取的對策是不同的,防止讀取到更改資料,隻需要對操作的資料添加行級鎖,阻止操作中的資料發生變化,而防止讀取到新增資料,則往往需要添加表級鎖——将整個表鎖定,防止新增資料。

第一類丢失更新 

    A事務撤銷時,把已經送出的B事務的更新資料覆寫了。這種錯誤可能造成很嚴重的問題,通過下面的賬戶取款轉賬就可以看出來: 

時間 取款事務A 轉賬事務B
T1 開始事務
T2 開始事務
T3 查詢賬戶餘額為1000元     
T4 查詢賬戶餘額為1000元
T5 彙入100元把餘額改為1100元
T6 送出事務
T7 取出100元把餘額改為900元
T8 撤銷事務
T9 餘額恢複為1000元(丢失更新)

  A事務在撤銷時,“不小心”将B事務已經轉入賬戶的金額給抹去了。 

第二類丢失更新 

  A事務覆寫B事務已經送出的資料,造成B事務所做操作丢失:  

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

    上面的例子裡由于支票轉賬事務覆寫了取款事務對存款餘額所做的更新,導緻銀行最後損失了100元,相反如果轉賬事務先送出,那麼使用者賬戶将損失100元。

事務隔離級别 

    盡管資料庫為使用者提供了鎖的DML操作方式,但直接使用鎖管理是非常麻煩的,是以資料庫為使用者提供了自動鎖機制。隻要使用者指定會話的事務隔離級别,資料庫就會分析事務中的SQL語句,然後自動為事務操作的資料資源添加上适合的鎖。此外資料庫還會維護這些鎖,當一個資源上的鎖數目太多時,自動進行鎖更新以提高系統的運作性能,而這一過程對使用者來說完全是透明的。 

    ANSI/ISO SQL 92标準定義了4個等級的事務隔離級别,在相同資料環境下,使用相同的輸入,執行相同的工作,根據不同的隔離級别,可以導緻不同的結果。不同僚務隔離級别能夠解決的資料并發問題的能力是不同的。 

        表 1 事務隔離級别對并發問題的解決情況 

隔離級别 髒讀 不可 重複讀 幻象讀 第一類丢失更新 第二類丢失更新
READ UNCOMMITED 允許 允許 允許 不允許 允許
READ COMMITTED 不允許 允許 允許 不允許 允許
REPEATABLE READ 不允許 不允許 允許 不允許 不允許
SERIALIZABLE 不允許 不允許 不允許 不允許 不允許

    事務的隔離級别和資料庫并發性是對立的,兩者此增彼長。一般來說,使用READ UNCOMMITED隔離級别的資料庫擁有最高的并發性和吞吐量,而使用SERIALIZABLE隔離級别的資料庫并發性最低。

    SQL 92定義READ UNCOMMITED主要是為了提供非阻塞讀的能力,Oracle雖然也支援READ UNCOMMITED,但它不支援髒讀,因為Oracle使用多版本機制徹底解決了在非阻塞讀時讀到髒資料的問題并保證讀的一緻性,是以,Oracle的READ COMMITTED隔離級别就已經滿足了SQL 92标準的REPEATABLE READ隔離級别。

    SQL 92推薦使用REPEATABLE READ以保證資料的讀一緻性,不過使用者可以根據應用的需要選擇适合的隔離等級。