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
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM