Spring(10)深入Spring 資料庫事務管理(一)
一、Spring 資料庫事務管理器的設計
在Spring中資料庫事務是通過PlatformTransactionManager 進行管理的,前面我們用到的jdbcTemplate是不支援事務的,而支援事務的是org.sringframework. transaction. support. Transaction Template 模板,它是 Spring 所提供的事務管理器的模板。
1.明確Spring事務控制
(1)JavaEE 體系進行分層開發,事務處理位於業務層,Spring 提供了分層設計業務層的事務處理解決方案。
(2)spring 框架為我們提供了一組事務控制的介面。這組介面是在spring-tx-5.0.2.RELEASE.jar 中。
(3)spring 的事務控制都是基於 AOP 的,它既可以使用程式設計的方式實現,也可以使用配置的方式實現。我們學習的重點是使用配置的方式實現。
2.Spring 資料庫事務管理器的設計
PlatformTransactionManager 介面的原始碼如下:
public interface PlatformTransactionManager { //獲取事務狀態 TransactionStatus getTransaction(TransactionDefinition definition ) throws TransactionException ; //提交事務void commit(TransactionStatus status) throws TransactionException ; //回滾事務 void rollback(TransactionStatus status) throws TransactionException ; }
在Spring 資料庫事務管理器設計中,DataSourceTransactionManager 它繼承抽象事務管理器 AbtractPlatformansactinManager,而AbstractPlatformTransactionManager又實現了PlatformTransactionManager,它們使用 PlatformTransactionManager介面方法,建立提交或者回滾事務。
3.Spring 中事務控制的API介紹
在實際開發中我們使用的都是PlatformTransactionManager具體實現類。
(1)PlatformTransactionManager
org.springframework.jdbc.datasource.DataSourceTransactionManager 使用 SpringJDBC 或 iBatis 進行持久化資料時使用
org.springframework.orm.hibernate5.HibernateTransactionManager使用Hibernate 版本進行持久化資料時使用
改介面中提供事務操作方法,包含以下3個具體操:
(2)TransactionDefinition
它是事務的定義資訊物件,裡面有如下方法:
二、資料庫相關知識
1.資料庫事務 ACID 特性
資料庫事務正確執行的 個基礎要素是原子性(Atomicity )、一致性( Consistency )、隔離性 Clso lation )和永續性( Durabi lity)。
(1)原子性: 整個事務中的所有操作,要麼全部完成,要麼全部不完成,不可能停滯在中間某個環節。事務在執行過程中發生錯誤,會被回滾到事務開始前的狀態,就像這個事務從來沒被執行過樣。
(2)一致性:指 個事務可以改變封裝狀態(除非它是 個只讀的〉。事務必須始終保持系統處於一致的狀態,不管在任何給定的時間併發事務有多少。
(3)隔離性: 它是指兩個事務之間的隔離程度。
(4)永續性: 在事務完成以後,該事務對資料庫所做的更改便持久儲存在資料庫之中,並不會被回滾。
2.丟失更新
在如今的網際網路生活中存在著秒殺、搶購等高併發場景,這樣就使得資料庫在一個多事務環境中執行,多個事務併發會引發一系列問題,主要問題就是丟失更新問題,一般存在兩種丟失更新問題,我下面分別舉例說明。
(1)第一類丟失更新(回滾丟失,Lost update)
假設這個場景,一張銀行卡存在網際網路消費和刷卡消費,剛好一對夫妻共用該卡進行消費,丈夫喜歡刷卡消費,妻子喜歡網際網路消費,那麼存在下面情景:
時刻 | 事務一(丈夫) | 事務二(妻子) |
T1 | 查詢餘額10000元 | —— |
T2 | —— | 查詢餘額10000元 |
T3 | —— | 網購消費1000元 |
T4 | 喝酒吃飯1000元 | —— |
T5 | 提交事務成功,餘額9000元 | —— |
T6 | —— | 退貨,回滾事務到T2時刻,餘額10000元 |
第一類丟失更新
整個過程中丈夫消費1000元,卡餘額9000元,而在最後T6時刻妻子退貨,事務回滾,卻恢復了卡餘額10000元,這顯然不符合事實,我們稱這種現象為第一類丟失更新。好在現在大部分資料庫都已經解決了該類丟失更新問題。
(2)第二類丟失更新(覆蓋丟失/兩次更新問題,Second lost update)
時刻 | 事務一(丈夫) | 事務二(妻子) |
T1 | 查詢餘額10000元 | —— |
T2 | —— | 查詢餘額10000元 |
T3 | —— | 網購花費1000元 |
T4 | 吃飯喝酒1000元 | —— |
T5 | 提交事務成功,查詢餘額9000元。(消費1000元后餘額為9000元) | —— |
T6 | 提交事務,根據之前查詢餘額10000元,消費1000元后,餘額9000元 |
第二類丟失更新
在整個場景中存在兩筆交易,一筆交易是丈夫吃飯喝酒消費,一筆是妻子購物消費,但是兩者都提交了事務,在不同事務中,無法預知其他事務相關操作,導致兩者提交事務後餘額都為9000元,實際上應該是8000元,這就是第二類丟失會更新。為了解決事務之間協助的一致性,資料庫標準規範中定義了事務之間的隔離級別,從而在一定程度上減少丟失更新的可能性。
3.事務的隔離級別
隔離級別可以在不同程度上減少丟失更新,根據SQL標準規範隔離級別分為四層,分別是髒讀( dirty read )、讀/寫提交( read commit )、可重複讀( repeatable read )和序列化( Serializable )。
(1)髒讀
是最低的隔離級別,表示允許 一個事務去讀取另 一個事務中未提交的資料。以上面例子進行講解:
時刻 | 事務一(丈夫) | 事務二(妻子) | 備註 |
T1 | 查詢餘額10000元 | —— | —— |
T2 | —— | 查詢餘額10000元 | —— |
T3 | —— | 網購花費1000元,餘額9000元 | —— |
T4 | 吃飯喝酒1000元,餘額8000元 | —— | 讀取到事務二,未提交餘額9000元事務,所以餘額8000元 |
T5 | 提交事務 | —— | 餘額8000元 |
T6 | —— | 回滾事務 |
由於第 一類丟失更新資料庫己經克服,所以餘額為錯誤的 8000元 |
由於在T3時刻妻子啟動了消費,導致餘額為8000 元,老公在T4時刻消費,因為用了髒讀,所以能夠讀取老婆消費的餘額(注意,這個餘額是事務二未提交的)為9000元,這樣餘額就為8000元了,於是T5時刻丈夫提交事務,餘額變為了8000 元,妻子在T6時刻回滾事務,由於資料庫克服了第一類丟失更新,所以餘額依舊為8000元,顯然這個錯誤的餘額,產生這個錯誤的根源來自於T4時刻,也就是事務一可以讀取事務二未提交的事務,這樣的場景被稱為髒讀。
(2)讀/寫提交
讀/寫提交,就是一個事務只能讀取另以個事務已經提交的資料。依舊以丟失更新妻子消費為例,如表:
時刻 | 事務一(丈夫) | 事務二(妻子) | 備註 |
T1 | 查詢餘額10000元 | —— | —— |
T2 | —— | 查詢餘額10000元 | —— |
T3 | —— | 網購消費1000元,餘額9000元 | —— |
T4 | 吃飯喝酒1000元,餘額9000元 | —— |
由於事務二的餘額未提交,採取讀/寫提 |
T5 | 提交事務 | —— | 餘額9000元 |
T6 | —— | 回滾事務 |
由於第一類丟失更新資料庫已經克服, |
在T3時刻,由於事務採取讀/寫提交的隔離級別,所以丈夫無法讀取妻子未提交的 9000 元餘額 ,他只能讀到餘額為 10 000 元,所以在消費後餘額依舊為 9000 。在 T5 時刻提事務,而 T6 時刻妻子回滾事務,所以結果為正確9000 元,這樣就消除了髒讀帶來的問題,但是也會引發其他的問題。接著看下面分析。
(3)可重複讀
時刻 | 事務一(丈夫) | 事務二(妻子) | 備註 |
T1 | 查詢餘額10000元 | —— | —— |
T2 | —— | 查詢餘額10000元 | —— |
T3 | —— | 網上購物花費1000元,餘額9000元 | —— |
T4 | 吃飯喝酒花費2000元,餘額8000元 | —— |
由於採取讀/寫提交 ,不能讀取事務 |
T5 | —— | 繼續購物8000元,餘額1000元 |
由於採取讀/寫提交 ,不能讀取事務 |
T6 | —— | 提交事務,餘額1000元 | 妻子提交事務,餘額更新為 1000 |
T7 |
提交事務發現餘額為1000 不足以買單 |
—— |
由於採用讀/寫提交,因此此時事務一 |
由於 T7 時刻事務一知道事務二提交的結果餘額為 1000 元,導致丈夫無錢買單的尷尬 對於老公而言,他並不知道妻子做了什麼事情,但是賬戶餘額卻莫名其妙地從 10 000 元變為了1 000 元,對他來說賬戶餘額是不能重複讀取的,而是一 個會變化的值,這樣的場景我們稱為不可重複讀( unrepeatable read ),這是讀/寫提交存在的問題。
為了克服不可重複讀帶來的錯誤, SQL 標準又提出了一個可重複讀的隔離級別來解問題。注意,可重複讀這個概念是針對資料庫同一條記錄而言的,換句話說,可重複讀會使得同一條資料庫記錄的讀/寫按照一個序列化進行操作,不會產生交叉情況,這樣就能證同一條資料的一致性,進而保證上述場景的正確性。但是由於資料庫並不是只能針對一條資料進行讀/寫操作,在很多場景下資料庫需要同時對多條記錄進行讀/寫,這個時候就會產生下面的情況,如下表所示:
時刻 | 事務一(丈夫) | 事務二(妻子) | 備註 |
T1 | —— | 查詢消費記錄有10條,準備列印 | 初始狀態 |
T2 | 啟用消費1筆 | —— | —— |
T3 | 提交事務 | —— | —— |
T4 | —— | 列印消費記錄得到11條 |
妻子發現列印了11條消費記錄,比查詢的10條多了 |
妻子在 Tl 詢到 10 條記錄,到 T4 列印記錄時,並不知道丈夫在 T2 T3 時刻進了消費,導致多 1條(可重複讀是針對同一條記錄而言的,而這裡不是同一 條記錄)消費記錄的產生 ,她會質疑這條多出來的記錄是不是幻讀出來的,這樣的場景我們稱為幻讀。(phantom read)。
(4)序列化
序列化的隔離級別它是一種讓SQL按照順序讀寫的方式,能夠消除資料庫事務之間併發產生資料不一致的問題。它有效的克服了幻讀。
各類隔離級別產生的現象:
隔離級別 | 髒讀 | 不可重複讀 | 幻讀 |
髒讀 | √ | √ | √ |
讀/寫提交 | χ | √ | √ |
可重複讀 | χ | χ | √ |
序列化 | χ | χ | χ |
4.選擇隔離級別
在實際工作中我們不但要考慮資料庫的一致性,而且還要考慮資料庫的效能,一般而言,從髒讀到序列化,系統性能直線下降。因此設定較高級別隔離比如序列化 會嚴重壓制併發,從而引發大量的執行緒掛起,直到獲得鎖才能進一步操作 而恢復時又需要大量的等時間。在大部分場景應用中 ,會選擇讀/寫提交的方式設定事務,這樣既有助於提高併發,又壓制士髒讀,但是對於資料 致性問題並沒有解決。基於應用不是所有業務都有併發操作,因此針對不需要或者併發業務少的場景可以考慮序列化級別保證資料的一致性。
5.傳播行為
傳播行為是指方法之間的呼叫事務策略的問題。大部分情況下我們希望事務能夠同時成功或失敗,但也有例外,比如實現信用卡還款,有一個總的呼叫程式碼邏輯一一-RepaymentBatchService batch 方法,它要實現的是記錄還款成功的總卡數和對應完成的資訊,每一 張卡的還款則是通過 RepaymentBatchService的repay方法完成的。
從上圖分析,假如只有一條事務,那麼呼叫-RepaymentBatchService的repay方法對某張信用卡進行還款,此時發生異常,如果此事務回滾就會造成所有相關資料進行回滾,那些正常還款的也會失敗,這不是正確的結果。此時我們要求batch方法呼叫repay方法時,自動未repay方法建立一個新的事務,發生異常時回滾而不影響其他事務。
上面描述的batch 方法去排程 repay 方法時能產生一條新事務,去處理 一個信用卡還款 ,如果該卡還款異常,那麼只會回滾這條新事務,而不是回滾主事務,類似這樣一個方法排程另外一個方法時,可以對事務的特性進行傳播配置,我們稱為傳播行為。
在Spring中 傳播行為的型別,是通過一個枚 舉型別去定義 的,這個列舉類是org .springfamework.transaction.annotation.Propagation,傳播行為有如下幾種:
(1)REQUIRED:如果當前沒有事務,就新建一個事務,如果已經存在一個事務中,加入到這個事務中。一般的選擇(預設值)
(2)SUPPORTS:支援當前事務,如果當前沒有事務,就以非事務方式執行(沒有事務)
(3)MANDATORY:使用當前的事務,如果當前沒有事務,就丟擲異常
(4)REQUERS_NEW:新建事務,如果當前在事務中,把當前事務掛起。
(5)NOT_SUPPORTED:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起
(6)NEVER:以非事務方式執行,如果當前存在事務,丟擲異常
(7)NESTED:如果當前存在事務,則在巢狀事務內執行。如果當前沒有事務,則執行 REQUIRED 類似的操作。
6.超時時間
預設值是-1,沒有超時限制。如果有,以秒為單位進行設定。
7.是否是隻讀事務
建議查詢時設定為只讀。
8.TransactionStatus
此介面提供的是事務具體的執行狀態,方法介紹如下圖: