spring 傳播行爲與數據庫事務ACID

數據庫事務ACID特性

  數據庫事務正確執行的4個基礎要素是原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和持久性(Durability)。
  •原子性:整個事務中的全部操做,要麼所有完成,要麼所有不完成,不可能停滯在中間某個環節。事務在執行過程當中發生錯誤,會被回滾到事務開始前的狀態,就像這個事務歷來沒被執行過同樣。
  •一致性:指一個事務能夠改變封裝狀態(除非它是一個只讀的)。事務必須始終保持系統處於一致的狀態,無論在任何給定的時間併發事務有多少。
  •隔離性:它是指兩個事務之間的隔離程度。
  •持久性:在事務完成之後,該事務對數據庫所作的更改便持久保存在數據庫之中,並不會被回滾。
  這裏的原子性、一致性和持久性都比較好理解,而隔離性就不同了,它涉及了多個事務併發的狀態。首先多個事務併發會產生數據庫丟失更新的問題,其次隔離性又分爲多個層級。
 

隔離級別

  隔離級別能夠在不一樣程度上減小丟失更新,那麼對於隔離級別數據庫標準是怎麼定義的呢?按照SQL的標準規範(還有些人認爲這是Spring或者Java的規範,而事實是SQL的規範,Spring或者Java只是按照SQL的規範定義的而已),把隔離級別定義爲4層,分別是:髒讀(dirty read)、讀/寫提交(readcommit)、可重複讀(repeatable read)和序列化(Serializable)。

   髒讀是最低的隔離級別,其含義是容許一個事務去讀取另外一個事務中未提交的數據。以丟失更新的消費爲例進行說明,如表所示。

 



  因爲在T3時刻老婆啓動了消費,致使餘額爲9 000元,老公在T4時刻消費,由於用了髒讀,因此可以讀取老婆消費的餘額(注意,這個餘額是事務二未提交的)爲9 000元,這樣餘額就爲8 000元了,因而T5時刻老公提交事務,餘額變爲了8 000元,老婆在T6時刻回滾事務,因爲數據庫克服了第一類丟失更新,因此餘額依舊爲8 000元,顯然這是一個錯誤的餘額,產生這個錯誤的根源來自於T4時刻,也就是事務一能夠讀取事務二未提交的事務,這樣的場景被稱爲髒讀。

  爲了克服髒讀,SQL標註提出了第二個隔離級別—— 讀/寫提交。所謂讀/寫提交,就是說一個事務只能讀取另外一個事務已經提交的數據。依舊以丟失更新的夫妻消費爲例,如表所示。

 



  在T3時刻,因爲事務採起讀/寫提交的隔離級別,因此老公沒法讀取老婆未提交的9 000元餘額,他只能讀到餘額爲10 000元,因此在消費後餘額依舊爲9 000元。在T5時刻提交事務,而T6時刻老婆回滾事務,因此結果爲正確的9 000元,這樣就消除了髒讀帶來的問題,可是也會引起其餘的問題,如表所示。

 




  因爲T7時刻事務一知道事務二提交的結果——餘額爲1 000元,致使老公無錢買單的尷尬。對於老公而言,他並不知道老婆作了什麼事情,可是帳戶餘額卻莫名其妙地從10 000元變爲了1000元,對他來講帳戶餘額是不能重複讀取的,而是一個會變化的值,這樣的場景咱們稱爲 不可重複讀(unrepeatable read),這是讀/寫提交存在的問題。

  爲了克服不可重複讀帶來的錯誤,SQL標準又提出了一個 可重複讀的隔離級別來解決問題。注意,可重複讀這個概念是針對數據庫同一條記錄而言的,換句話說,可重複讀會使得同一條數據庫記錄的讀/寫按照一個序列化進行操做,不會產生交叉狀況,這樣就能保證同一條數據的一致性,進而保證上述場景的正確性。可是因爲數據庫並非只能針對一條數據進行讀/寫操做,在不少場景,數據庫須要同時對多條記錄進行讀/寫,這個時候就會產生下面的狀況,如表所示。

 



  老婆在T1查詢到10條記錄,到T4打印記錄時,並不知道老公在T2和T3時刻進行了消費,致使多一條(可重複讀是針對同一條記錄而言的,而這裏不是同一條記錄)消費記錄的產生,她會質疑這條多出來的記錄是否是幻讀出來的,這樣的場景咱們稱爲幻讀(phantom read)。

  爲了克服幻讀,SQL標準又提出了 序列化的隔離級別。它是一種讓SQL按照順序讀/寫的方式,可以消除數據庫事務之間併發產生數據不一致的問題。關於各種的隔離級別和產生的現象如表所示。

 



選擇隔離級別和傳播行爲  

  選擇隔離級別的出發點在於兩點:性能和數據一致性,下面展開論述。
  在互聯網應用中,不但要考慮數據庫數據的一致性,並且要考慮系統的性能。通常而言,從髒讀到序列化,系統性能直線降低。所以設置高的級別,好比序列化,會嚴重壓制併發,從而引起大量的線程掛起,直到得到鎖才能進一步操做,而恢復時又須要大量的等待時間。所以在購物類的應用中,經過隔離級別控制數據一致性的方式被排除了,而對於髒讀風險又過大。在大部分場景下,企業會選擇讀/寫提交的方式設置事務。這樣既有助於提升併發,又壓制了髒讀,可是對於數據一致性問題並無解決,後面會詳細討論如何去克服這類問題。對於通常的應用均可以使用@Transactional方法進行配置
  代碼清單:使用讀/寫提交隔離級別
@Autowired
private RoleDao roleDao = null;
//設置方法爲讀/寫提交的隔離級別 
@Transaction(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
public int insertRole(Role role) {
    return roleDao.insert(role);
}

 

  固然也會有例外,並非說全部的業務都在高併發下完成,當業務併發量不是很大或者根本不須要考慮的狀況下,使用序列化隔離級別用以保證數據的一致性,也是一個不錯的選擇。總之,隔離級別須要根據併發的大小和性能來作出決定,對於併發不大又要保證數據安全性的可使用序列化的隔離級別,這樣就可以保證數據庫在多事務環境中的一致性
  代碼清單:使用序列化隔離級別
@Autowired
private RoleDao roleDao = null;

//設置方法爲序列化的隔離級別 
@Transaction(propagation = Propagation.REQUIRED, isolation = Isolation.SERIALIZABLE)
public int insertRole(Role role) {
    return roleDao.insert(role);
}

 


  只是這樣的代碼會使得數據庫的併發能力低下,在搶購商品的場景下出現卡頓的狀況,因此在高併發的場景下這段代碼並不適用

  在實際工做中,註解@Transactional隔離級別的默認值爲Isolation.DEFAULT,其含義是默認的,隨數據庫默認值的變化而變化。由於對於不一樣的數據庫而言,隔離級別的支持是不同的。好比MySQL能夠支持4種隔離級別,而默認的是可重複讀的隔離級別。而Oracle只能支持讀/寫提交和序列化兩種隔離級別,默認爲讀/寫提交,這些是在工做中須要注意的問題。

傳播行爲

  傳播行爲是指方法之間的調用事務策略的問題。在大部分的狀況下,咱們都但願事務可以同時成功或者同時失敗。可是也會有例外
  在Spring中傳播行爲的類型,是經過一個枚舉類型去定義的,這個枚舉類是org.springframework.transaction.annota-tion.Propagation,它定義瞭如表所列舉的7種傳播行爲。

 


文章來源:ssm 13.4,13.5
相關文章
相關標籤/搜索