談談Java事務

事務具基本特徵(ACID)


Atomi(原子性):事務中包含的操作被看做一個整,要麼完全部成功,要麼全部失敗。

② Consistency(一致性):事務在完成時,必須是所有的數據都保持一致狀態,保證了數據的完整性和一致性。

③ Isolation(隔離性):當多個用戶並發訪問資料庫時,比如操作同一張表時,資料庫為每一個用戶開啟的事務,不能被其他事務的操作所干擾,多個並發事務之間要相互隔離。

這裡的隔離性就是下面我們要說的隔離級別,為了減少事務在修改數據上的相互影響。

④ Durability(持久性):一個事務一旦被提交了,那麼對資料庫中的數據的改變就是永久性的,即便資料庫系統遇到故障也不會丟失提交事務的操作。

事務的傳播行為


事務傳播行為(propagation behavior)指的就是當一個事務方法A被另一個事務方法B調用時,這個事務方法A應該如何進行。換句話說,需要發生方法件的調用,才會存在傳播行為,對於單個事務方法而已,沒有傳播的概念。

傳播行為 含義 應用場景
REQUIRED 默認傳播行為,當前上下文不存在事務,所以會開啟一個新的事務,當上下文存在事務,則融入當前事務(它是個機會主義者,既可以暗度陳倉,也可以獨當一面) 大部分簡單場景
SUPPORTS 如果存在一個事務,支援當前事務。如果沒有事務,則非事務的執行(它是個保守派,隨波逐流,從不另起爐灶) 查詢
MANDATORY 如果存在一個事務,支援當前事務。如果沒有一個活動的事務,則拋出異常(它是個獨裁者,只許州官放火,不許百姓點燈)
REQUIRES_NEW 它會開啟一個新的事務。如果一個事務已經存在,則先將這個存在的事務掛起。(它是個革新派,開天闢地) 批量操作時,需要對單調數據進行控制
NOT_SUPPORTED 總是非事務地執行,並掛起任何存在的事務(他是個悲觀主義者,不主動,誰來都拒絕)
NEVER 總是非事務地執行,如果存在一個活動事務,則拋出異常(怎麼說,這個有點反人類)
NESTED 嵌套事務一個非常重要的概念就是內層事務依賴於外層事務。外層事務失敗時,會回滾內層事務所做的動作。而內層事務操作失敗並不會引起外層事務的回滾。(害,活像一個愛情弱勢方,受害者,左右不了對方,被對方左右)

事務的隔離級別


準確來說,事務的隔離級別只有四個,分別為:

  • 讀未提交(READ_UNCOMMITTED)
  • 讀已提交(READ_COMMITTED)
  • 可重複讀(REPEATABLE_READ)
  • 串列化(SERIALIZABLE)

單單看這幾個字有點干,其他的先不管,先來看一下他們都能解決啥問題,以及無法解決什麼問題先

異常現象 臟讀 不可重複讀 幻讀
未提交讀
讀寫提交 ×
可重複讀 × ×
串列化 × × ×

如何理解上面這個表格呢?首先,如果是簡單的查詢,那是不存在的什麼問題的,因為你只進行了查詢而已。那麼如果你有對資料庫的更新修改操作,是不是就會產生問題呢?不一定,如果這個時間內只有一個事務,那你做再多的操作,都是不會有問題的,要麼這個事務的操作全部成功,要麼全部失敗。所以在對事務產生問題的討論上,都是針對並發事務。這一點與事務的傳播性的特點一樣。簡單的理解,就是同一時間,對同一數據有多個事務在操作。

那我們都知道,每一個事務的里的操作,都會被電腦分割成很多的原子操作,由cpu進行調度執行,所以就涉及這些操作的一個排列問題,但我們知道cpu的調度是隨機的,所以的就會產生很多可能性。這些操作的執行順序,導致了面臨一些問題。

最低級別讀未提交(READ_UNCOMMITTED),什麼問題都有可能發生,不適合併發事務場景

由上面所提到的事務的持久性可以得知,事務的持久化,是建立在事務被提交的基礎上的。也就是說,事務A沒提交,對數據的操作都不算真正的生效,那麼如果在未提交前,被別的事務B的讀取到這部分數據,那麼你最終提交的化還好,嚴格來說不算臟數據,但假如事務A回滾了,那讀的這部分數據其實是錯誤的,我們把它稱之為「臟數據」,這是並發事務面臨的第一個問題。

要解決「臟數據」問題,就必須保證一個事務不會讀到另一個並行事務已修改但未提交的數據,這正是讀已提交(READ_COMMITTED)隔離級別所要求的。換句話說就是「我修改的數據還沒提交你不能讀」。解決了臟數據問題後。我讀到的數據的確都是「生效」的了。

但這會帶來一個問題,單事務A需要對一個數據多次讀取的時候,中間可能存在一個可能:事務B修改這個數據了,由該級別讀到的數據是已提交可知,事務B對數據的修改操作肯定是生效的了,所以事務A多次讀到的結果可能不一致,這就是「不可重複讀」問題。

要解決「不可重複讀」問題,很顯然,我就得加強約束,上一級是我修改的數據還沒提交你不能讀,這次是當我讀數據開始,到我所在的事務還沒提交之前,你不能讀,這意味著不管事務A讀數據後有多少操作,並發事務B都得等待,可以看到的「鎖住」的範圍更大了,也相應帶來更大的損耗。此時解決重複讀的問題,級別是可重複讀(REPEATABLE_READ)

但可重複讀級別解決的是同一競爭數據的重複讀問題,假如事務A多次通過特定條件多次讀取m條數據,有事務C,插入n條數據服務事務A查詢條件,或者修改了其他t條數據同樣符合事務A的條件,那事務A後面讀取可能就是m+n或m+t條數據了,此為」幻讀「現象,要解決此問題

要解決幻讀現象,只能祭出最後殺招,串列化(SERIALIZABLE)級別

總結如下

隔離等級 含義
READ_UNCOMMITTED 最低等級,從上表可以看到,啥問題解決不了,所以當並發事務操作同一數據時,啥情況都可能發生,所以一般不用於並發事務場景
READ_COMMITTED 保證了一個事務不會讀到另一個並行事務已修改但未提交的數據
REPEATABLE_READ 保證了一個事務不會修改已經由另一個事務讀取但未提交(回滾)的數據
SERIALIZABLE 最嚴格的級別,事務串列執行,資源消耗最大

資料庫實現


未提交讀的資料庫鎖情況(實現原理)

事務在讀數據的時候並未對數據加鎖。

務在修改數據的時候只對數據增加行級共享鎖

提交讀的資料庫鎖情況

事務對當前被讀取的數據加 行級共享鎖(當讀到時才加鎖),一旦讀完該行,立即釋放該行級共享鎖

事務在更新某數據的瞬間(就是發生更新的瞬間),必須先對其加 行級排他鎖,直到事務結束才釋放。

可重複讀的資料庫鎖情況

事務在讀取某數據的瞬間(就是開始讀取的瞬間),必須先對其加 行級共享鎖,直到事務結束才釋放;

事務在更新某數據的瞬間(就是發生更新的瞬間),必須先對其加 行級排他鎖,直到事務結束才釋放。

可序列化的資料庫鎖情況

事務在讀取數據時,必須先對其加 表級共享鎖 ,直到事務結束才釋放;

事務在更新數據時,必須先對其加 表級排他鎖 ,直到事務結束才釋放。

參考資料


資料庫隔離級別 及 其實現原理