spring事務詳解(一)概覽
系列目錄
引子
很多coder在不理解事務的原理甚至連基本概念都不清楚的情況下,就去使用資料庫事務,是極容易出錯,寫出一些自己不能掌控的程式碼。網上很多文章要不就是概念,或者一點原始碼,或者一點測試驗證,都不足以全面瞭解事務,所以本文出現了,本系列Spring事務詳解包含四部分:
第一章 講概念,對事務的整體有一個瞭解。
第二章 從原始碼來看底層實現機制。
第三章 例項測試驗證。
第四章 總結提高。
這次編寫,期望一起學習提高,個人能力有限,有任何不當之處,麻煩指出。
全文基於Mysql innodb引擎。Mysql官方文件:官網飛機票 ,推薦書籍:《Mysql技術內幕-InnoDB儲存引擎》。
一、背景
spring事務領頭人叫Juergen Hoeller,于爾根·糊了...先混個臉熟哈,他寫了幾乎全部的spring事務程式碼。讀原始碼先拜神,掌握他的原始碼的風格,讀起來會通暢很多。
事務(Transaction)是資料庫區別於檔案系統的重要特性之一。目前國際認可的資料庫設計原則是ACID特性,用以保證資料庫事務的正確執行。Mysql的innodb引擎中的事務就完全符合ACID特性。
二、事務的ACID特性
-
原子性(Atomicity):一個事務必須被視為一個不可分割的最小工作單元,整個事務中的所有操作要麼全部提交成功,要麼全部失敗回滾。--》主要涉及InnoDB事務。相關特性:事務的
-
一致性(consistency):資料庫總是從一個一致性的狀態轉換到另一個一致性的狀態。在事務開始前後,資料庫的完整性約束沒有被破壞。例如違反了唯一性,必須撤銷事務,返回初始狀態。--》主要涉及內部InnoDB處理,以保護資料不受崩潰,相關特性:雙寫緩衝、崩潰恢復。
-
隔離性(isolation):每個讀寫事務的物件對其他事務的操作物件能相互分離,即:事務提交前對其他事務是不可見的,通常內部加鎖實現。--》主要涉及事務,尤其是事務隔離級別,相關特性:隔離級別、innodb鎖的底層實現細節。
-
永續性(durability):一旦事務提交,則其所做的修改會永久儲存到資料庫
fsync()的支援、
備份策略等。
三、事務的屬性
要保證事務的ACID特性,spring給事務定義了6個屬性,對應於宣告式事務註解(org.springframework.transaction.annotation.Transactional)@Transactional(key1=*,key2=*...)
- 事務名稱:使用者可手動指定事務的名稱,當多個事務的時候,可區分使用哪個事務。對應註解中的屬性value、transactionManager
- 隔離級別: 為了解決資料庫容易出現的問題,分級加鎖處理策略。 對應註解中的屬性isolation
- 超時時間: 定義一個事務執行過程多久算超時,以便超時後回滾。可以防止長期執行的事務佔用資源.對應註解中的屬性timeout
- 是否只讀:表示這個事務只讀取資料但不更新資料, 這樣可以幫助資料庫引擎優化事務.對應註解中的屬性readOnly
- 傳播機制: 對事務的傳播特性進行定義,共有7種類型。對應註解中的屬性propagation
- 回滾機制:定義遇到異常時回滾策略。對應註解中的屬性rollbackFor、noRollbackFor、rollbackForClassName、noRollbackForClassName
其中隔離級別和傳播機制比較複雜,咱們細細地品一品。
3.1 隔離級別
這一塊比較複雜,我們從3個角度來看:3種錯誤現象、mysql的底層技術支援、分級處理策略。這一小節一定要好好看,已經開始涉及核心原理了。
1.現象(三種問題)
髒讀(Drity Read):事務A更新記錄但未提交,事務B查詢出A未提交記錄。
不可重複讀(Non-repeatable read): 事務A讀取一次,此時事務B對資料進行了更新或刪除操作,事務A再次查詢資料不一致。
幻讀(Phantom Read): 事務A讀取一次,此時事務B插入一條資料事務A再次查詢,記錄多了。
2. mysql的底層支援(一致性非鎖定讀VS鎖定讀)
-
一致性非鎖定讀(mysql隱性處理)
一致性非鎖定讀(consistent nonlocking
read)是指InnoDB儲存引擎通過多版本控制(multi
versionning)的方式來讀取當前執行時間資料庫中行的資料,如果讀取的行正在執行DELETE或UPDATE操作,這是讀取操作不會因此等待行上鎖的釋放。相反的,InnoDB會去讀取行的一個快照資料
上面展示了InnoDB儲存引擎一致性的非鎖定讀。之所以稱為非鎖定讀,因為不需要等待訪問的行上X鎖的釋放。快照資料是指該行之前版本的資料,該實現是通過undo段來完成。而undo用來事務中的回滾資料,因此快照資料本身沒有額外的開銷,此外,讀取快照資料不需要上鎖,因為沒有事務需要對歷史資料進行修改操作。在MVCC中,讀操作可以分成兩類,快照讀(Snapshot read)和當前讀(current read)。
快照讀:普通的select
當前讀:
- select * from table where ? lock in share mode; (加S鎖)
- select * from table where ? for update; (加X鎖)
- insert, update, delete 操作前會先進行一次當前讀(加X鎖)
-
鎖定讀(使用者自己顯式使用)
innoDB對select語句支援兩種鎖定讀(意向鎖Intention Locks):
1)SELECT...FOR UPDATE:對讀取的行加排它鎖(X鎖),其他事務不能對已鎖定的行再加任何鎖。
2 ) SELECT...LOCK IN SHARE MODE :對讀取的行加共享鎖(S鎖),其他事務可以再加S鎖,X鎖會阻塞等待。
注:這兩種鎖都必須處於事務中,事務commit,鎖釋放。所以必須begin或者start transaction 開啟一個事務或者索性set autocommit=0把自動提交關掉(mysql預設是1,即執行完sql立即提交)
3. 分級處理策略(四種隔離級別)
官網描述:
InnoDB使用不同的鎖定策略支援每個事務隔離級別。對於關鍵資料的操作(遵從ACID原則),您可以使用強一致性(預設Repeatable Read)。對於不是那麼重要的資料操作,可以使用Read Committed/Read Uncommitted。Serializable執行比可重讀更嚴格的規則,用於特殊場景:XA事務,併發性和死鎖問題的故障排除。
四種隔離級別:
1.Read Uncommitted(讀取未提交內容):可能讀取其它事務未提交的資料。(髒讀+不可重複讀+幻讀)
2.Read Committed(讀取提交內容):一個事務只能看見已經提交事務所做的改變。(RC隔離級別下,一致性非鎖定讀的資料快照是最新版本的。)但其他事務可能會有新的commit,所以同一select可能返回不同結果。(不可重複讀+幻讀)
3.Repeatable Read(可重讀):
1.實現可重複讀:同一事務內多次一致性非鎖定讀,RR會取第一次讀取時建立的快照版本,從而保證了同一事務內部的可重複讀,這也是innodb的預設隔離級別。
2.解決幻讀問題,採用加鎖解決。鎖定讀(FOR UPDATE or LOCK IN SHARE MODE), UPDATE, 和 DELETE,具體鎖一行還是鎖多行,如下:
1)對於具有唯一搜索條件的唯一索引,InnoDB只鎖定找到的索引記錄。
2)對於其他搜尋條件,InnoDB會對掃描的索引範圍進行鎖定,使用next-key locks,阻塞其他session對間隙的insert操作。所以RR隔離級別下,使用者必須自己使用加鎖讀解決幻讀問題!!!。
4.Serializable(可序列化):這是最高的隔離級別,它是在每個讀的資料行上加上共享鎖(LOCK IN SHARE MODE)。在這個級別,可能導致大量的超時現象和鎖競爭,主要用於分散式事務。
如下表:
不同隔離級別/可能出現的問題 | 髒讀 | 不可重複讀 | 幻讀 |
Read Uncommitted(讀取未提交內容) | ✅ | ✅ | ✅ |
Read Committed(讀取提交內容) | ❎ | ✅ | ✅ |
Repeatable Read(可重讀) | ❎ | ❎ |
✅ |
Serializable(可序列化) | ❎ | ❎ | ❎ |
上表需要特別注意的是RR級別下mysql innodb還是有幻讀現象的,只是需要使用者必須自己使用加鎖讀解決幻讀問題!
3.2 傳播機制
org.springframework.transaction包下有一個事務定義介面TransactionDefinition,定義了7種事務傳播機制,很多人對傳播機制的曲解從概念開始,所以特地翻譯了一下原始碼註釋如下:
1.PROPAGATION_REQUIRED
支援當前事務;如果不存在,建立一個新的。類似於同名的EJB事務屬性。這通常是事務定義的預設設定,通常定義事務同步作用域。
2.PROPAGATION_SUPPORTS
支援當前事務;如果不存在事務,則以非事務方式執行。類似於同名的EJB事務屬性。
注意:
對於具有事務同步的事務管理器,PROPAGATION_SUPPORTS與沒有事務稍有不同,因為它可能在事務範圍內定義了同步。因此,相同的資源(JDBC的Connection、Hibernate的Session等)將在整個指定範圍內共享。注意,確切的行為取決於事務管理器的實際同步配置!
小心使用PROPAGATION_SUPPORTS!特別是,不要依賴PROPAGATION_REQUIRED或PROPAGATION_REQUIRES_NEW,在PROPAGATION_SUPPORTS範圍內(這可能導致執行時的同步衝突)。如果這種巢狀不可避免,請確保適當地配置事務管理器(通常切換到“實際事務上的同步”)。
3.PROPAGATION_MANDATORY
支援當前事務;如果當前事務不存在,丟擲異常。類似於同名的EJB事務屬性。
注意:
PROPAGATION_MANDATORY範圍內的事務同步總是由周圍的事務驅動。
4.PROPAGATION_REQUIRES_NEW
建立一個新事務,如果存在當前事務,則掛起當前事務。類似於同名的EJB事務屬性。
注意:實際事務掛起不會在所有事務管理器上開箱即用。這一點特別適用於JtaTransactionManager,它需要TransactionManager的支援。
PROPAGATION_REQUIRES_NEW範圍總是定義自己的事務同步。現有同步將被掛起並適當地恢復。
5.PROPAGATION_NOT_SUPPORTED
不支援當前事務;始終以非事務方式執行。類似於同名的EJB事務屬性。
注意:實際事務掛起不會在所有事務管理器上開箱即用。這一點特別適用於JtaTransactionManager,它需要TransactionManager的支援。
事務同步在PROPAGATION_NOT_SUPPORTED範圍內是不可用的。現有同步將被掛起並適當地恢復。
6.PROPAGATION_NEVER
不支援當前事務;如果當前事務存在,丟擲異常。類似於同名的EJB事務屬性。
注意:事務同步在PROPAGATION_NEVER範圍內不可用。
7.PROPAGATION_NESTED
如果當前事務存在,則在巢狀事務中執行,如果當前沒有事務,類似PROPAGATION_REQUIRED。EJB中沒有類似的功能。
注意:實際建立巢狀事務只對特定的事務管理器有效。開箱即用,這只適用於 DataSourceTransactionManager(JDBC 3.0驅動)。一些JTA提供者也可能支援巢狀事務。
四、總結
本節講解了事務的4大特性和6大屬性的概念。並簡單拓展了一下概念。可能大家會比較懵逼哈,不用擔心只需要心裡有個概念就可以了,下一章咱們從底層原始碼來看事務的實現機制。
=======參考========
Mysql官方文件:官網飛機票
書籍:《Mysql技術內幕-InnoDB儲存引擎》