事務特性,事務的隔離級別以及spring中定義的事務傳播行為

  • 2020 年 3 月 16 日
  • 筆記

什麼是事務?

事務邏輯上的一組操作,組成這組操作的各個邏輯單元,要麼一起成功,要麼一起失敗。


事務特性

原子性 (atomicity):強調事務的不可分割,組成事務操作的各個最小邏輯單元缺一不可,強調了操作的完整性
一致性 (consistency):事務的執行前後,數據的完整性保持一致,強調數據的完整性
隔離性 (isolation):一個事務執行的過程中,不應該受到其他事務的干擾,強調事務的獨立性
持久性(durability) :事務一旦結束,數據應持久到數據庫中,強調了事務的可持久性


不同的隔離性引發的安全性問題

臟讀 :一個事務讀取到了另一個事務的未提交的數據。這些數據可能會被提交,也可能不會被提交。提交了還好,如果未提交,就讀取到了臟數據。
不可重複讀 :一個事務讀取到了另一個事務已經提交了的 update 數據,導致多次查詢結果不一致.。側重點是更新,重複讀取數據時,若有其它事務對這些數據進行了更新操作,會導致查詢到數據不一致。
幻讀 :一個事務讀取到了另一個事務已經提交了的 insert 數據,導致多次查詢結果不一致。側重點是插入,重複讀取數據時,若有其它事務進行了插入操作,會導致查詢到數據條數不一致。


事務的隔離級別

讀取未提交(read uncommited):臟讀,不可重複讀,幻讀都有可能發生。
讀取已提交 (read commited):避免了臟讀,但是不可重複讀和幻讀都有可能發生。
可重複讀 (repeatable read):避免了臟讀和不可重複讀,但是幻讀有可能發生。
串行化 (serializable):避免了以上問題,但效率較低。

Mysql 默認的事務隔離級別:可重複讀
Oracle 默認的事務隔離級別:讀取已提交


spring中定義的事務傳播行為

* 保證同一個事務中
PROPAGATION_REQUIRED:支持當前事務,如果不存在,就新建一個事務(默認屬性)
PROPAGATION_SUPPORTS:支持當前事務,如果不存在,就不使用事務
PROPAGATION_MANDATORY:支持當前事務,如果不存在,拋出異常

* 保證沒有在同一個事務中
PROPAGATION_REQUIRES_NEW:如果有事務存在,掛起當前事務,創建一個新的事務
PROPAGATION_NOT_SUPPORTEDd :以非事務方式運行,如果有事務存在,掛起當前事務
PROPAGATION_NEVER :以非事務方式運行,如果有事務存在,拋出異常
PROPAGATION_REQUIRED_NESTED: 如果當前事務存在,則嵌套事務執行


事務傳播行為的解讀

ServiceA { 
    static void methodA() 
        ServiceB.methodB(); 
    } 


ServiceB { 
    static void methodB() {
    } 
}

PROPAGATION_REQUIRED (加入當前正要執行的事務不在另外一個事務里,那麼就起一個新的事務)

比如說:

ServiceB.methodB()的事務級別定義為PROPAGATION_REQUIRED,那麼由於執行ServiceA.methodA的時候,ServiceA.methodA()已經起了事務,這時調用ServiceB.methodB(),ServiceB.methodB()看到自己已經運行在ServiceA.methodA()的事務內部,就不會再起新的事務。

而假如ServiceA.methodA()運行的時候發現自己沒有在事務中,他就會為自己分配一個事務。這樣,在ServiceA.methodA()或者在ServiceB.methodB()內的任何地方出現異常,事務都會被回滾。即ServiceB.methodB()的事務已經被提交,但是ServiceA.methodA()在接下來fail要回滾,ServiceB.methodB()也要回滾。

PROPAGATION_SUPPORTS(如果當前在事務中,即以事務的形式運行,如果當前不再一個事務中,那麼就以非事務的形式運行)

和平常不加事務的代碼區別很小,只是多了一個在事務中就以事務的形式運行的條件。

PROPAGATION_MANDATORY(必須在一個事務中運行)

也就是說:

他只能被一個父事務調用。否則,拋出異常。

PROPAGATION_REQUIRES_NEW(如果有事務存在,掛起當前事務,創建一個新的事務)

比如說:

我們設計ServiceA.methodA()的事務級別為PROPAGATION_REQUIRED,ServiceB.methodB()的事務級別為PROPAGATION_REQUIRES_NEW,那麼當執行到ServiceB.methodB()的時候,ServiceA.methodA()所在的事務就會掛起,ServiceB.methodB()會起一個新的事務,等待ServiceB.methodB()的事務完成以後,他才繼續執行。

他與PROPAGATION_REQUIRED的事務區別在於事務的回滾程度了。因為ServiceB.methodB()是新起一個事務,那麼就是存在兩個不同的事務。如果ServiceB.methodB()已經提交,那麼ServiceA.methodA()失敗回滾,ServiceB.methodB()是不會回滾的。如果ServiceB.methodB()失敗回滾,如果他拋出的異常被ServiceA.methodA()捕獲,ServiceA.methodA()事務仍然可能提交。

PROPAGATION_NOT_SUPPORTED(以非事務方式運行,如果有事務存在,掛起當前事務)

比如說:

ServiceA.methodA()的事務級別是PROPAGATION_REQUIRED ,而ServiceB.methodB()的事務級別是PROPAGATION_NOT_SUPPORTED ,那麼當執行到ServiceB.methodB()時,ServiceA.methodA()的事務掛起,而他以非事務的狀態運行完,再繼續ServiceA.methodA()的事務。

PROPAGATION_NEVER(以非事務方式運行,如果有事務存在,拋出異常)

假設ServiceA.methodA()的事務級別是PROPAGATION_REQUIRED, 而ServiceB.methodB()的事務級別是PROPAGATION_NEVER ,那麼ServiceB.methodB()就要拋出異常了。

PROPAGATION_NESTED(如果當前事務存在,則嵌套事務執行)

ServiceA { 
    void methodA() 
        try { 
            //savepoint 
            ServiceB.methodB(); //PROPAGATION_NESTED 級別 
        } catch (SomeException) { 
            // 執行其他業務, 如 
            ServiceC.methodC(); 
        } 
    } 
}

理解Nested的關鍵是savepoint。他與PROPAGATION_REQUIRES_NEW的區別是PROPAGATION_REQUIRES_NEW另起一個事務,將會與他的父事務相互獨立,而Nested的事務和他的父事務是相依的,他的提交是要等和他的父事務一塊提交的。

也就是說,如果父事務最後回滾,他也要回滾的。而Nested事務的好處是他有一個savepoint。也就是說ServiceB.methodB()失敗回滾,那麼ServiceA.methodA()也會回滾到savepoint點上,ServiceA.methodA()可以選擇另外一個分支,比如ServiceC.methodC(),繼續執行,來嘗試完成自己的事務。