1. 程式人生 > >spring事務詳解(一)概覽

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)一旦事務提交,則其所做的修改會永久儲存到資料庫

    --》涉及到MySQL軟體特性與特定硬體配置的相互影響,相關特性:4個配置項:雙寫緩衝開關、事務提交重新整理log的級別、binlog同步頻率、表文件;寫快取、作業系統對於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

當前讀

  1. select * from table where ? lock in share mode; (加S鎖)
  2. select * from table where ? for update; (加X鎖)
  3. 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)。在這個級別,可能導致大量的超時現象和鎖競爭,主要用於分散式事務

如下表:

mysql-innodb可能出現的問題統計表
不同隔離級別/可能出現的問題 髒讀 不可重複讀 幻讀
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儲存引擎》