在常見的關聯式資料庫中(例如:MySQL、PostgreSQL、MS SQL),都會有 ACID 的特性,而 ACID 即是:
也因為有 ACID 這四個特性,使得 MySQL 這類的資料庫支援 「Transaction(交易)」 的操作,所以接下來我們就直接用一個例子,來了解一下什麼是「Transaction(交易)」,以及了解 ACID 在其中的用途是什麼。
在資料庫中,有一種用法叫做 Transaction,而他的中文可以被翻譯成「交易管理」或是「事務管理」。
而 Transaction 的用途,就可以在「一個 Transaction 裡面,包含多個資料庫的操作,並且這些資料庫的操作,要嘛全部一起成功,要嘛全部一起失敗,也就是 All or Nothing 原則」。
舉例來說,假設我們現在要實作一個轉帳的功能,讓 A 可以轉 1000 元給 B,那麼在實作轉帳的功能時,就需要對資料庫進行兩次操作:也就是先將 A 的存款減去 1000 元,再將 B 的存款增加 1000 元,這樣子就可以完成轉帳的實作。
所以像是在上面的 transfer()
方法中,就是會先去對 A 的帳戶減去 1000 元(A 的存款從 3000 變成 2000),再為 B 的帳戶增加 1000 元(B 的存款從 500 變成 1500),進而完成轉帳的操作。
不過,在這段 transfer()
的程式中,其實是有一個潛在的問題的。
舉例來說,我們在執行這段程式時,一開始一樣是先從 A 的帳戶中減去 1000 元(A 的存款從 3000 變 2000),但是!假設這時程式突然出現了錯誤(有可能是程式出現 bug、或是機器故障、機房停電…等因素),導致在扣完 A 的錢之後,程式沒辦法繼續往下運行,再去 B 的帳戶中增加 1000 元。
因此最終結果就會變成 A 損失了 1000 元、但是 B 卻沒收到 1000 元,導致 1000 元就這樣消失了!這種狀況一定是大家不想看見的。
所以為了解決這個問題,Transaction 就被發明出來了!
在資料庫中,他支援一種用法叫做 「Transaction(交易)」,當我們使用 Transaction 來管理這整個轉帳的流程之後,那麼「扣掉 A 的錢」以及「增加 B 的錢」這兩件事情,他們就只能夠一起成功,或是一起失敗。
舉例來說,在 Spring Boot 程式中,我們可以在某個方法上面加上 @Transacional
,這樣子就可以宣告「使用一個 Transaction 來管理這個方法」,因此在這個例子中,當我們在 transfer()
方法上面添加 @Transactional
時,就表示我們創建了一個 Transaction,來管理這個 transfer()
方法。
而當某個方法被 Transaction 來管理時,那麼 「在那個方法中的所有資料庫操作,要嘛全部一起成功、要嘛全部一起失敗,也就是 All or Nothing 原則」。
因此在這個例子中,首先 A 一樣是會先被減去 1000 元沒錯(所以 A 的存款從 3000 變成 2000),然後這時候一樣,程式遇到了錯誤,因此程式就只能夠被迫中斷,沒辦法繼續往下執行,為 B 的帳戶增加 1000 元。
但是這個時候,因為我們有在 transfer()
方法上面加上 @Transactional
,用一個 Transaction 來管理此方法,因此此時 Transaction 就會復原(也稱為 rollback)前面做過的所有資料庫操作,也就是會將 1000 元還給 A,所以 A 的存款又從 2000 元變回 3000 元,就像是剛剛那個扣除的 SQL 語法從來沒有執行過一樣,A 和 B 的存款都回到最初的樣子,這就是 Transaction 的厲害之處!!
所以對於轉帳這種「涉及多個資料庫操作」的功能而言,如果我們想要確保轉帳中的所有資料庫操作要嘛一起成功、要嘛一起失敗,這時候就可以使用資料庫所支援的 Transaction 用法,使用 Transaction 來保護這整筆轉帳的交易過程了。
也因為 Transaction 會確保方法中的所有資料庫操作,要嘛全部一起成功、要嘛全部一起失敗,因此這也稱為是 All or Nothing 原則。
補充:Transaction 只能確保「同一個資料庫」中的交易會一起成功、一起失敗而已,如果涉及到多個資料庫(像是分散式資料庫),那麼使用 Transaction 的情況會變得複雜得多,大家有興趣的話,可以再上網查詢「分散式交易(Distributed Transactions)」的相關資訊。
了解了 Transaction(交易)要解決的問題之後,我們終於可以說回到最一開始的「資料庫的 ACID」的特性了!
我們在前面有提到,常見的關聯式資料庫中(例如:MySQL、PostgreSQL、MS SQL),都會有 ACID 的特性,而 ACID 即是:
也因為有 ACID 這四個特性,所以 MySQL 這類的資料庫才能支援 Transaction 的操作。所以換句話說的話,假設某個資料庫他不具備 ACID 的特性的話,那他是不能使用 Transaction 的操作的!!!
舉例來說,假設你使用的資料庫是 NoSQL(例如 MongoDB、Elastic Search),那麼即使你有在 Spring Boot 程式中添加 @Transactional
,指定這段方法要使用 Transaction 來管理,但是因為 NoSQL 他不支援 ACID 啊!所以他就不支援 Transaction 的操作🥹。
因此不管你在程式中加了多少個 @Transactional
,那段程式仍舊是沒有受到 Transaction 的交易保護的,所以就算程式運行中出現錯誤,仍舊是沒辦法 rollback 成原始數據的,這是大家在使用 Transaction 時,一定要注意的細節!
補充:軟體變化瞬息萬變,現在也已經有些 NoSQL 資料庫支援 ACID 的特性、進而支援 Transaction 了,大家在使用之前,建議也可以參考一下你使用的 NoSQL 資料庫介紹。
而如果我們把 ACID 展開來,一一對應到 Transaction 中的概念的話,就可以發現他真的每一條都是為了 Transaction 來設計的:
特性 | 描述 |
---|---|
Atomicity(原子性) | Transaction 是一個不可被分割的單元 |
Consistency(一致性) | Transaction 執行的前後,必須確保數據的完整性是一致的 |
Isolation(隔離性) | 資料庫同時處理多個 Transaction 時,各個 Transaction 之間不能互相影響 |
Durability(永久性) | Transaction 一旦提交之後,他對資料庫所做的改變永久有效,不會因為系統重啟或錯誤而改變 |
也因為傳統的 SQL 資料庫滿足了 ACID 的特性,因此我們作為後端工程師,才能夠在程式中使用 @Transational
,進而控制某一段程式要使用一個 Transaction 來管理,感謝 ACID!!
所以透過上面的介紹,現在我們就了解到,資料庫的 ACID 和 Transaction(交易)可以說是息息相關的!
我們可以使用 Transaction 來包住一段程式,讓裡面的所有資料庫操作要嘛一起成功、要嘛一起失敗,也就是 All or Nothing 原則。
而 ACID 則代表的是:
特性 | 描述 |
---|---|
Atomicity(原子性) | Transaction 是一個不可被分割的單元 |
Consistency(一致性) | Transaction 執行的前後,必須保持數據的完整性是一致的 |
Isolation(隔離性) | 資料庫同時處理多個 Transaction 時,各個 Transaction 之間不能互相影響 |
Durability(永久性) | Transaction 一旦提交之後,他對資料庫所做的改變永久有效,不會因為系統重啟或錯誤而改變 |
所以大家如果使用的是傳統的 SQL 資料庫,就可以善加利用 ACID 的特性和 Transaction 的功能,確保你的數據不會因為中途的意外而產生髒資料了~
這篇文章我們有介紹了資料庫的 ACID 特性、並且也介紹了 Transaction 的概念,希望可以讓大家更了解傳統 SQL 資料庫的特性!
如果你對後端技術有興趣的話,也歡迎免費訂閱 《古古的後端筆記》電子報 ,每週二為你送上一篇後端技術分享,那我們就下一篇文章見啦!