Spring 事務處理超級詳細詳解

事務是數據庫邏輯上的一組操做,一個事務中的一組操做,要麼都執行,要麼都不執行。
複製代碼

事務的四大特性(ACID)

Atomicity原子性:整個事務中的全部操做,要麼所有完成,要麼所有不完成,不可能停滯在中間某個環節。事務在執行過程當中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務歷來沒有執行過同樣。mysql

Consistency一致性:整個事務執行先後數據庫是保持一致的,保證數據庫數據的完整性和正確性。算法

Isolation隔離性:各個併發事務之間不會互相干擾,多個併發事務之間互相隔離。(4個隔離級別)sql

Durability持久性:持久性是指一個事務一旦被提交了,那麼對數據庫中的數據的改變就是永久性的,即使是在數據庫系統遇到故障的狀況下也不會丟失提交事務的操做。數據庫

redo,undo,binlog二進制編程

事務日誌

redolog 重作日誌,用來保證事務的持久性,在Innodb存儲引擎下,Insert,Delete,Update操做記錄的都是redo物理日誌,記錄的是數據頁的物理變化,主要用於數據庫的崩潰恢復。 Redo日誌能夠分爲兩部分,一個是內存中存儲的redo日誌緩存(redo log buffer),是容易丟失的,一個是存儲在本地磁盤的redo日誌文件,是持久的。 redo日誌整個產生流程:緩存

  • 第一步:先將原始數據從磁盤中讀入內存中來,修改數據的內存拷貝安全

  • 第二步:生成一條重作日誌並寫入redo log buffer,記錄的是數據被修改後的值bash

  • 第三步:當事務commit時,將redo log buffer中的內容刷新到 redo log file,對 redo log file採用追加寫的方式session

  • 第四步:按期將內存中修改的數據刷新到磁盤中(innodb_flush_log_at_trx_commit設置策略0,1,2)併發

innodb_flush_log_at_trx_commit: 0: 每秒刷新到磁盤log(在mysql故障時可能會丟失最後1秒的數據) 1:每次提交都刷新日誌到磁盤log(mysql默認) 2:每次提交都刷新到osbuffer(系統緩存),但每秒才刷新到磁盤log(在mysql故障時不影響,操做系統故障丟失最後1s數據)

undo log日誌

undo主要記錄的是數據的邏輯變化,爲了在發生錯誤時回滾到以前的狀態,須要把以前的操做記錄下來。

undo日誌,只將數據庫邏輯地恢復到原來的樣子,在回滾的時候,它其實是作的相反的工做,好比一條INSERT ,對應一條 DELETE,對於每一個UPDATE,對應一條相反的 UPDATE,將修改前的行放回去。undo日誌用於事務的回滾操做進而保障了事務的原子性。

在InnoDB存儲引擎中,undo存儲在回滾段(Rollback Segment)中,每一個回滾段記錄了1024個undo log segment,而在每一個undo log segment段中進行undo 頁的申請,在5.6之前,Rollback Segment是在共享表空間裏的,5.6.3以後,可經過 innodb_undo_tablespace設置undo存儲的位置。

undo的類型 在InnoDB存儲引擎中,undo log分爲:

insert undo log

update undo log

insert undo log是指在insert 操做中產生的undo log,由於insert操做的記錄,僅對事務自己可見,對其餘事務不可見,故該undo log能夠在事務提交完成後直接刪除。

update undo log是指在update和delete操做中產生的undo log,該undo log可能須要提供mvcc機制(多版本併發控制),不能在事務提交後就刪除undo log,等待purge線程進行最後的刪除。(purge線程是指在Innodb存儲引擎中,delete操做並非直接刪除數據,而是在要刪除的數據上標識Delete_Bit,也就是平時所說的邏輯刪除,purge線程會去清除帶有Delete_Bit標識的數據)

undo日誌並非redo日誌的逆過程,redo日誌記錄的是物理日誌,是持久存在的,而undo日誌是邏輯日誌,對事物回滾時,只是把數據庫恢復到以前的狀態,每一個undo的生命週期只是從事務開始到事務結束。

假設有A、B兩個數據,值分別爲1,2.

1. 事務開始
2. 記錄A=1到undo log
3. 修改A=3
4. 記錄A=3到 redo log
5. 記錄B=2到 undo log
6. 修改B=4
7. 記錄B=4到redo log
8. 將redo log寫入磁盤
9. 事務提交
複製代碼

binlog二進制日誌

二進制日誌是在存儲引擎之上的層面,redo和undo是Innodb引擎中的操做日誌,而binlog二進制日誌是在引擎之上,所以無論數據庫採用Innodb默認存儲引擎仍是其餘存儲引擎,都會產生binlog。 雖然binlog和redolog都是記錄對數據庫的操做,可是二者卻不同: 1.binlog記錄不論是什麼引擎,都會記錄對數據庫的操做,而redolog記錄的是InnoDB引擎下對錶的操做,而且binlog先於redolog記錄。 2.binlog在commit後一次性寫入緩存中的日誌文件,而redolog則在數據準備修改前寫入redobufferlog緩存中,寫入完成後纔會執行數據修改操做,待事務完成後會刷新到持久性redolog磁盤文件中。 3.事務日誌是記錄的是物理頁的變化,具備冪等性,記錄方式比較簡潔。好比在一個事務中進行了某行數據的添加,刪除,又添加,最終事務日誌記錄的只是最後添加的記錄,也就是物理頁的變化。而二進制日誌則會把這幾回操做所有記錄下來,記錄比較多。

sync_binlog:sync_binlog 是 MySQL 的二進制日誌(binlog)同步到磁盤的頻率。MySQL server 在 binary log 每寫入 sync_binlog 次後,刷寫到磁盤。 若是 autocommit 開啓,每一個語句都寫一次 binary log,不然每次事務寫一次。默認值是 0,不主動同步,而依賴操做系統自己不按期把文件內容 flush 到磁盤。設爲 1 最安全,在每一個語句或事務後同步一次 binary log,即便在崩潰時也最多丟失一個語句或事務的日誌,但所以也最慢。

大多數狀況下,對數據的一致性並無很嚴格的要求,因此並不會把 sync_binlog 配置成 1. 爲了追求高併發,提高性能,能夠設置爲 100 或直接用 0. 而和 innodb_flush_log_at_trx_commit 同樣,對於支付服務這樣的應用,仍是比較推薦 sync_binlog = 1.

事務的加鎖方式

爲了維護事務隔離性與一致性,通常數據庫採用的加鎖的方式。以Mysql爲例,主要有表鎖與行鎖,固然如今也提供了MetaData元數據鎖。因爲數據庫是一個高併發應用,若是加鎖過分就會極大下降數據庫的併發處理能力,因此這裏分析一下InnoDB引擎數據庫的加鎖機制。

數據庫遵循兩段鎖協議,將一個事務分爲兩個階段,即加鎖階段與解鎖階段。

加鎖階段:表鎖通常應用與數據庫DDL操做,整張表加鎖,加鎖過程當中不容許任何操做。因爲表鎖太過無解,因此又出現了行鎖。行鎖只做用於那一行數據,其餘行的DML操做都不會受到影響。行鎖又分爲共享鎖(S鎖,其餘事務能夠繼續加共享鎖,不能加排它鎖)與排它鎖(X鎖,其餘事務不能加任何鎖)。

共享鎖:又稱爲讀鎖,其餘事務能夠一塊讀取這行數據,可是不能進行DML操做。

排它鎖:又稱爲寫鎖,其餘任何事務不能進行加鎖操做,可是能夠正常讀取數據(讀取結果爲事務以前的數據),由於Mysql InnoDB引擎默認爲update,insert,delete語句加上排他鎖,select語句默認不加鎖。(每句sql都是一個事務,可經過select lock in share mode爲select加共享鎖,經過select for update爲select加排它鎖)。

解鎖階段:事務commit提交後釋放鎖。

隔離級別

在數據庫的併發操做中,爲了保證數據的正確性,也就是維護事務的隔離性與一致性。這些數據庫鎖也正是爲這些而存在的。
複製代碼

  • 未提交讀(Read Uncommited):容許髒讀,也就是能夠讀取到其餘未提交的事務修改的數據。
  • 已提交讀(Read Committed):只能讀取到已提交的數據。(Oracle默認隔離級別)
  • 可重複讀(Repeatable Read):可重複讀,在同一個事務中,查詢到的數據都是事務開始時候的數據,InnoDB默認隔離級別,消除了不可重複讀,可是會存在幻讀。(當一個事務開始執行過程當中(沒有加鎖),另一個事務獲取鎖修改了這條數據並提交,那麼這個事務讀取到的數據仍是最開始時候的數據。)
  • 串行化(Serializable):徹底串行化,獲取表鎖,讀寫都會阻塞。

經過 set session/global transaction isolation level +隔離級別設置隔離級別

不可重複讀與幻讀的區別

不可重複讀主要在於update與delete,而幻讀主要在於insert。可重讀經過在讀取時候加鎖,可實現可重複讀。可是卻沒法鎖住insert數據,當事務A先前讀取了數據,或者修改了所有數據,事務B仍是能夠insert數據提交,這時事務A就會發現莫名其妙多了一條以前沒有的數據,這就是幻讀。幻讀能夠經過表鎖來實現,也就是Serializable,可是會極大下降併發能力。

可是mysql是如何解決幻讀問題的呢?

下面就瞭解一下樂觀鎖和悲觀鎖。

悲觀鎖:正如其名,它指的是對數據被外界(包括本系統當前的其餘事務,以及來自外部系統的事務處理)修改持保守態度,所以,在整個數據處理過程當中,將數據處於鎖定狀態。悲觀鎖的實現,每每依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,不然,即便在本系統中實現了加鎖機制,也沒法保證外部系統不會修改數據)。在悲觀鎖的狀況下,爲了保證事務的隔離性,就須要一致性鎖定讀。讀取數據時給加鎖,其它事務沒法修改這些數據,相似select... for update這樣的語句。修改刪除數據時也要加鎖,其它事務沒法讀取這些數據。

樂觀鎖:相對悲觀鎖而言,樂觀鎖機制採起了更加寬鬆的加鎖機制來解決事務的隔離性。悲觀鎖大多數狀況下依靠數據庫的鎖機制實現,以保證操做最大程度的獨佔性。但隨之而來的就是數據庫性能的大量開銷,特別是對長事務而言,這樣的開銷每每沒法承受。 樂觀鎖,使用CAS實現,而CAS算法大可能是基於數據版本( Version )記錄機制實現。即在表中添加一個version字段,讀取出數據時,將此版本號一同讀出,以後更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,若是提交的數據版本號大於數據庫表當前版本號,則予以更新,不然認爲是過時數據。

目前成熟的數據庫如Mysql,Oracle都是使用了以樂觀鎖爲理論基礎的MVCC(多版本併發控制)來避免幻讀和不可重複讀。

源碼解析

Spring支持兩種的事務使用:編程式事務與聲明式事務。
複製代碼

編程式事務:經過在業務代碼中對事務作手動回滾,對代碼入侵性強,不推薦使用.(TransactionAspectSupport,TransactionTemplate) 聲明式事務:使用@Transactional註解。 Spring事務中,主要包含TransactionDefinition,TransactionStatus,PlatformTransactionManager,所謂的事務管理,其實就是"按照給定的事務規則執行事務的提交或回滾操做",TransactionDefination就表示給定的事務規則,TransactionStatus表示運行着的事務狀態,PlatformTransactionManager用來執行事務操做。

TransactionDefinition

TransactionDefinition用於定義一個事務,包括事務的傳播熟悉,隔離級別,超時時間,只讀等屬性,默認使用DefaultTransactionDefinition,也支持自定義配置。

PlatformTransactionManager

PlatformTransactionManager用於事務的執行操做,接口定義以下:

public interface PlatformTransactionManager {
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
    void commit(TransactionStatus status) throws TransactionException;
    void rollback(TransactionStatus status) throws TransactionException;
}
複製代碼

根據底層所使用的不一樣的持久化 API 或框架,PlatformTransactionManager 的主要實現類大體以下:

  • DataSourceTransactionManager:適用於使用JDBC和iBatis進行數據持久化操做的狀況。

  • HibernateTransactionManager:適用於使用Hibernate進行數據持久化操做的狀況。

  • JpaTransactionManager:適用於使用JPA進行數據持久化操做的狀況。

  • 另外還有JtaTransactionManager JdoTransactionManager、JmsTransactionManager等等。

TransactionStatus

TransactionStatus表示事務的狀態,經過PlatformTransactionManager.getTransaction()獲取,TransactionStatus接口提供了一個簡單的事務控制和事務查詢的方法。

public  interface TransactionStatus{
   boolean isNewTransaction();
   void setRollbackOnly();
   boolean isRollbackOnly();
}
複製代碼

聲明式事務主要是經過Spring AOP實現的,對使用@Transactional註解的方法進行攔截,AOP經過代理方式執行目標方法,

根據invokeWithinTransaction方法建立PlatformTransactionManager,TransactionStatus,TransactionDefinition,從源碼中能夠看到TransactionInfo,表示事務對象,對前面幾個進行封裝。

class TransactionInfo{
    private final PlatformTransactionManager transactionManager;

    private final TransactionAttribute transactionAttribute;

	private final String joinpointIdentification;

	private TransactionStatus transactionStatus;

	private TransactionInfo oldTransactionInfo;
}
複製代碼

, 經過代理執行目標方法,若是方法執行完畢成功則執行commitTransactionAfterReturning進行事務提交,經過TransactionInfo.getTransactionManager().cimmit(),若是方法執行過程當中出現異常並捕獲到,則執行completeTransactionAfterThrowing進行事務回滾,TransactionInfo.getTransactionManager().rollback();

經過ThreadLocal本地線程變量管理TransactionInfo,表示Spring事務是線程安全的。
相關文章
相關標籤/搜索