# RDBMS 的交易事務 [![hackmd-github-sync-badge](https://hackmd.io/p98jzc9cToyNJwVAjgsXuw/badge)](https://hackmd.io/p98jzc9cToyNJwVAjgsXuw) --- # 交易簡介 交易(Transaction)是一個單元工作(unit of work),包括一個起始點,標註交易開始,這個單元工作包括了數個步驟來完成,交易結束時的動作有兩個,數個步驟全部執行成功,交易才算成功並提交變更(**Commit**),只有當中有一個失敗,則整個交易宣告失敗並回復所有變更(**Rollback**)。 也就是說交易是為了滿足 **ACID** 中所謂的 **原子性(Atomicity)** 而生。 --- # 交易的種類 ---- ## Flat Transaction 交易中無其它交易,為一個單元操作。 ```sql begin transaction XXX ... end transaction XXX ``` ---- ## Nested Transaction 在交易中包括其它交易,外部交易要成功,內部交易必須全部成功,應用程式可以重試內部交易,若有外部交易回復,則內部所有交易都要回復。 ```sql begin transaction XXX begin transaction OOO ... end transaction OOO begin transaction ZZZ ... end transaction ZZZ end transaction XXX ``` ---- ## Chained Transaction 交易不僅有單一提交點,而可以有多個中斷點(break point),若要回復,則回復動作將至上一個中斷點,而非整個交易被回復。 ```sql begin transaction OOO ... Commit exception Rollback end transaction OOO begin transaction ZZZ ... Commit exception Rollback end transaction ZZZ ``` --- # 隔離層級 隔離性是交易的保證之一,表示交易與交易之間不互相干擾,好像同時間就只有自己的交易存在一樣,隔離性保證的基本方式是在資料庫層面,對資料庫或相關欄位鎖定,在同一時間內只允許一個交易進行更新或讀取。 如果說交易是為了滿足原子性(Atomicity),那麼隔離可以說就是為了保證**一致性(Consistency)**。 --- # 沒有隔離的世界 在介紹隔離作法前,我們應該要先知道,在交易的世界中若沒有隔離會發生什麼問題 ~~防疫大潰堤~~ ---- ## 更新遺失(Lost Update) - A 曾 Commit 的變更是否還在? 1. 交易 A 更新欄位 1 2. 交易 B 更新欄位 1 3. 交易 A Commit 4. 交易 B Rollback - B Commit 的結果 會被覆蓋? 1. 交易 A 更新欄位 1 2. 交易 B 更新欄位 1 3. 交易 B Commit 4. 交易 A Commit ---- ## 髒讀(Dirty Read) - B 根據 A 更新的內容做了一些處理,但 A Rollback 了更新內容 1. 交易 A 更新欄位 1 2. 交易 B 讀取欄位 1 3. 交易 A Rollback 4. 交易 B Commit ---- ## 無法重複的讀取(Unrepeatable Read) - A 前後所看到的資料不同 1. 交易 A 讀取欄位 1 2. 交易 B 更新欄位 1 3. 交易 B Commit 4. 交易 A 讀取欄位 1 ---- ## 幻讀(Phantom Read) - 到底有幾筆資料 1. 交易 A 進行查詢得到五筆資料 2. 交易 B 插入一筆資料 3. 交易 B Commit 4. 交易 A 進行查詢得到六筆資料 --- # 遵守規定好好隔離 隔離交易的基本方式是鎖定資料庫,但資料庫實務上並不會完全的鎖定,以避免造成嚴重的效能問題。 實務上會根據資料讀寫更新的頻繁性,設定不同的**交易隔離層級**。 ---- ## Read Uncommited > 放手前都屬於我的 - 保證:「A 交易已更新但尚未確認的資料,B 交易僅可作讀取動作」 - A 交易在更新但未提交,B 交易的更新會被延後至 A 提交/復原之後 - 至少可避免 **lost update**的問題 ---- ## Read Commited > 打扮過後方可見人的精緻女子 - 保證:「交易讀取的資料必須是已確認的資料」 - 讀取的交易不會阻止其它的交易 - 一個未確認的更新交易 - 阻止其它所有的交易 - 尚未確定前其他交易只能先操作暫存表格 - 至少可避免 **Dirty Read 以下**問題。 ---- ## Repeatable Read > 你一如往常地美好 - 保證:「同一交易內兩次讀取的資料必須相同」 - 讀取交易不會阻止其它讀取的交易 - 阻止其它寫入的交易 - 尚未確定前其他交易只能先操作暫存表格 - 至少可避免 **Unrepeatable Read 以下**問題。 ---- ## Serializable > 風不平浪不靜心還不安穩 > 一個島鎖住一個人 - 只要有資料不一致的疑慮,交易就必須循序 - A 做讀取交易 - B 要等 A 結束才能更新 - A 做更新交易 - B 要等 A 更新完才能讀取 - B 要等 A 更新完才能更新 - 世界大同、城市美好,輕鬆慢活~~令人嚮往~~ ---- ## 被隔離的小結 | Isolation Level | Dirty Read | Unrepeatable Read | Phantom Read | | :--------------: | :--------: | :---------------: | :----------: | | Read Uncommitted | YES | YES | YES | | Read Committed | NO | YES | YES | | Repreatable Read | NO | NO | YES | | Serializable | NO | NO | NO | --- # 更加工程化的運作 根據各類 Library 提供的方式不同,主要會分為 - **宣告式(Declarative)**:於檔案定義交易邊界、隔離層級等,無需修改程式原始碼 - 編程式(Programmatical):直接使用 JDBC 或相關框架的 API --- # 大量的副程式 AP 與 DB 最大的不同在於封裝了更多的業務行為,細分了大量不同用途的副程式。 > 在茫茫的城市之海中 > 我們再難以一目望見所有交易邊界 > 只得靠著旗號傳播宣示領地的歸屬 ---- ## PROPAGATION_REQUIRED - 當前程式必須在一個具有事務的上下文中執行 - 已經有事務在進行,被呼叫端將在該事務中執行 - 否則就重新開啟一個新的事務 ---- ## PROPAGATION_SUPPORTS - 當前方法不必須在一個具有事務的上下文中執行 - 已經有事務在進行,被呼叫端將在該事務中執行 - 否則...就算了 ---- ## PROPAGATION_MANDATORY - 當前程式必須在一個已存在的事務中執行 - 已經有事務在進行,被呼叫端將在該事務中執行 - 否則...我就讓你跟這個世界說再見 ---- ## PROPAGATION_REQUIRES_NEW - 強制自己開啟一個新的事務 - 已經有事務在進行,先掛起該事務,我將另起爐灶 ---- ## PROPAGATION_NOT_SUPPORTED - 當前程式不在任何事務中執行 - 已經有事務在進行,先掛起該事務,我無拘無束 ---- ## PROPAGATION_NEVER - 當前程式不在任何事務中執行 - 已經有事務在進行,誰都別想他媽的拘束我 ---- ## PROPAGATION_NESTED - 毫無反應,就是個 [Nested Transaction](#Nested-Transaction) ---- ## 傳播模式一覽 | 交易區間策略 | 說 明 | | :-----------: | :-----------------------------------------------------------------------: | | REQUIRED | 支援現在的交易,如果沒有的話就建立一個新的交易 | | REQUIRES_NEW | 建立一個新的交易,如果現存一個交易的話就先暫停,並啟始一個新的交易來執行 | | SUPPORTS | 支援現在的交易,如果沒有的話就以非交易的方式執行 | | MANDATORY | 方法必須在一個現存的交易中進行,否則丟出例外 | | NOT_SUPPORTED | 指出不應在交易中進行,如果有的話就暫停現存的交易 | | NEVER | 指出不應在交易中進行,如果有的話就丟出例外 | ---- ## 說得更明白一點 - 本身不在交易中,而呼叫另一個方法時 - 在非交易中進行(SUPPORTS、NOT_SUPPORTED、NEVER) - 起始新的交易(REQUIRED、REQUIRES_NEW) - 丟出例外(MANDATORY) - 本身在交易中,而呼叫另一個方法時 - 在客戶端的交易中進行(REQUIRED、SUPPORTS、MANDATORY) - 暫停客戶端交易,起始新的交易(REQUIRED_NEW) - 暫停客戶端交易,於非交易環境中執行(NOT_SUPPORTED) - 丟出例外(NEVER) --- # Commit 還是 Rollback 這是個問題 離開被宣告事務的程式就將結束事務,而這個事務的 Commit 與 Rollback,就取決於你是正常的 Return 還是收到例外。 ---- ## 正常回傳,世界和平 ---- ## 收到例外 - 天哪,是例外(拋 - 大家一起回到那最初始最乾淨的世界開端 --- # 我好像可以處理這個例外 讓我們**謹慎小心的處理它吧** --- # PROPAGATION_REQUIRED ---- ## 我主宰了這場交易 - 不,你不可以 - 即便你優雅地接住了這個例外,你也無法保留你的成果 ---- ## 在別人的局中搗亂 - 主宰交易的人可以挽回這一切嗎 - 他如果想嘗試接住這個例外,他只會在最後遭到失敗 --- # PROPAGATION_SUPPORTS ---- ## 別傻了 - **在這方面,他與 REQUIRED,並沒什麼不同** - 順帶一提 **MANDATORY** 那個必須在事務中執行的傢伙也是一樣 --- # PROPAGATION_REQUIRES_NEW ---- ## 我主宰了這場交易 - 當例外發生,**誰都無法**留下成果 ---- ## 在別人的局中搗亂 - 不,你不可以 - 主宰這場交易的人可以嗎 - 老實說,雖然 **NEW** 本人仍然無法保留成果 - 但在他之上的其他人只要有良好的解決例外,就不受影響 ---- ## 靜靜地看著上頭胡鬧 - 嘿,當我新起的事務結束,那我當然也將成果 Commit 了 --- # PROPAGATION_NESTED ---- ## 吸收別人的優點 - 巢狀內的一切失敗,都可以不影響外界(跟 **NEW** 一樣友善) - 當然,外界的人要好好地接下這個例外 ---- ## 做出自己的特點-最佳馬仔 - 老闆與長官犯錯,小弟當然也有錯 - **NEW 會在結束時自己 Commit** - **NESTED** 需要等到包裹住自己的其他事務 Commit,才會被一併 Commit ---- ## 結論 - NEW - 本身發生例外,上層也許仍可以正常處理 - 上層發生例外,我就是要寫入變更 - NESTED - 自己發生例外,上層也許仍可以正常處理 - 上層發生例外,負責任地一併還原 - 其他 - 祈禱最好不要發生錯誤 - 發生錯誤時,至少一切都會被還原 --- # 參考 - [簡介交易](https://openhome.cc/Gossip/EJB3Gossip/TransactionABC.html) - [簡介隔離層級](https://openhome.cc/Gossip/HibernateGossip/IsolationLevel.html) - [簡介交易區間](https://openhome.cc/Gossip/EJB3Gossip/TransactionDemarcation.html)
{"metaMigratedAt":"2023-06-16T12:45:54.554Z","metaMigratedFrom":"YAML","title":"RDBMS 的交易事務","breaks":false,"slideOptions":"{\"spotlight\":{\"enabled\":true}}","contributors":"[{\"id\":\"ba4a3e61-8b42-4ac0-8490-74a2fd86e992\",\"add\":6134,\"del\":1}]"}
    218 views