​資料庫事務的三個元問題

語言: CN / TW / HK

✏️ 編者按:

在《一文解析資料庫的三生三世》這篇文章中,我們站在歷史的角度認識了資料庫的「前世今生」。文中提到線上事務處理等關鍵場景,那究竟什麼是資料庫的事務?為什麼資料庫需要支援事務?為了實現資料庫事務,各種資料庫是如何設計的?讓我們一起來看看資料庫事務的三個元問題吧!

首發|AI 前線

作者|Zilliz


  什麼是資料庫事務

事務,就是用來保證資料操作符合業務邏輯要求而實現的一系列功能。換句話說,如果資料庫不支援事務,上層業務系統的程式設計師就需要自己寫程式碼,以保證相關資料處理邏輯的正確性。舉個例子,資料庫最開始普及就是在金融業,銀行的存取款場景就是一個最典型的 OLTP 資料庫場景,而事務就是用來保證類似場景的業務邏輯正確性的。

(圖片來自網路,侵刪)

原子性:如果你要給家人轉賬,必須在你的賬戶里扣掉 100 塊,在家人賬戶里加上 100 塊,這兩筆操作需要一起完成,業務邏輯才是正確的。但是程式在做修改時,肯定會有先後順序,試想一下程式扣了你的錢,這個時候程式崩潰了,家人賬戶的錢沒有加上,那這 100 塊是不是消失了?你是不是要發瘋?那麼,就把這兩筆操作放進一個事務裡,通過原子性保證,這兩筆操作要麼都成功,要麼都失敗。這樣才能保證業務邏輯的正確性。

一致性:有很多文章講過一致性,但是很多人會把一致性跟原子性混在一起說。事務的一致性指的是,每一個事務必須保證執行之後所有庫內的規則依舊成立,比如內外來鍵、constraint、觸發器等。舉例來說,你在儲蓄卡里有 100 元,理財賬戶裡有 100 元,基金賬戶有 100 元,那麼你在資產總和裡會看到 300 元,這 300 元必須是三個賬戶餘額加在一起得到的。你從儲蓄卡里轉出去了 100 元給家人,那麼可以在資料庫上建立觸發器,當儲蓄卡餘額賬戶減 100 元的同時,把資產總和也同步減去 100 元,不然就會出現邏輯上的錯誤。你已經轉走了 100 元儲蓄卡餘額,實際資產總和應該是 200 元,若還是 300 元,資料庫狀態就不一致了。因此實現事務的時候,必須要保證相關聯的觸發器以及其他內部規則都執行成功,事務才算執行成功。如果在減去資產總額時出錯,資料庫就會進入不一致的狀態那麼這筆轉帳交易也不能成功。

那麼一致性跟原子性的區別到底在哪裡呢?原子性是指多個使用者指令之間必須作為一個整體完成或失敗,而一致性更多是資料庫內的相關資料規則同時完成或失敗。

永續性:事務只要提交了,對資料庫的修改就會儲存下來不會丟了。簡單來說,只要提交了,資料庫就算崩潰了,重啟之後你剛存的 100 塊依然在你的賬戶裡。

隔離性:每個事務相對於其他的事務有一定獨立性、不能互相影響,因為資料庫需要支援併發的操作來提高效率。在併發操作時,一定要通過操作之間的隔離來保證業務邏輯的正確性。比如,你轉帳 100 塊給家人,一系列操作的最後一步可能是輸入驗證碼,這個時候轉帳還沒有完成,但是在資料庫裡,你的賬戶對應的記錄中已經減去 100 塊,家人賬戶也加了 100 塊,就等著驗證碼輸入以後,事務提交,完成操作。那麼,這個時候,家人通過手機銀行能夠查到這 100 塊麼?你的答案可能是不能,因為你的轉帳操作還沒有提交,事務還沒有完成,那麼資料庫就應該保證這兩個併發操作之間具有一定的隔離性,這樣才符合業務邏輯。

到底應該隔離到什麼程度呢?隔離性又分為 4 個等級:由低到高依次為 Read uncommitted(讀未提交)、Read committed(讀提交)、Repeatable read(可重複讀取)、Serializable(序列化),這四個級別可以逐個解決髒讀、不可重複讀、幻象讀這幾類問題。

怎麼理解不同的隔離等級呢?首先要理解併發操作,併發操作就是指有不同的使用者同時對一個數據進行讀、寫操作,那麼在這個過程中,每個使用者應該看到什麼資料才能保證業務邏輯的正確性呢?

如果是前面存取款的場景,我看到的是已經存進來的錢,也就是必須是已經提交的事務。而 12306 刷火車票,你可以看到有 10 張餘票,但是在下單的時候告訴你票賣完了,因為同時有 10 個使用者把票買掉了,你需要重新刷餘票。這個也是可以接受的。也就是說我可以讀到一些虛假的餘票,在業務上也沒有什麼問題。那麼在設計這兩個不同系統時,就可以選擇不同的事務隔離級別來實現不同的併發效果。不同的隔離等級就是在系統的併發性和資料邏輯的嚴謹性之間做出的平衡。



  資料庫如何實現事務

資料庫實現事務會有多種不同的方式,但基本的原理類似,比如都需要對事務進行統一的編號處理,都需要記錄事務的狀態(是成功了還是失敗了),都需要在資料儲存的層面對事務進行支援,以明確哪些資料是被哪些事務插入、修改和刪除的。同時還會記錄事務日誌等,對事務進行系統化的管理以實現資料的原子性、一致性和永續性。
要實現事務的隔離性,最基礎的就是通過加鎖機制,把併發操作適當序列化來保證資料操作的正確邏輯。但是為了要保證系統具有良好的併發效能,必須要在實現事務隔離性時找到合理的平衡點。
大部分資料庫(包括 Oracle、MySQL、Postgres 在內)在做併發控制的時候,都會採用 MVCC(多版本併發控制)的機制來保證系統具有較高的併發性。不同資料庫實現 MVCC 的具體方案不盡相同,但其基本原理類似。

  MVCC 實現原理

所謂 MVCC,就是資料庫中的同一查詢根據相關事務執行的先後順序以及隔離級別的不同,可能會存在不同版本的結果,通過這樣的手段來保證大部分查詢操作不會被修改操作阻塞並保證資料邏輯的正確性。簡單來說就是,用儲存空間來交換併發能力。

下面以 Postgres 為例介紹一下 MVCC 的一種實現方式,下圖用以解釋 Posrgres 裡最基本的資料可見性是如何實現多版本控制的。

(圖片來自網路,侵刪)

首先,Postgres 裡的每一個事務都有編號,這裡可以簡單理解為時間順序編號,編號越大的事務發生越晚。然後,資料庫裡的每一行記錄都會儲存建立這條記錄的事務號(Cre),也會在記錄刪除時儲存刪除這條記錄的事務號(Exp),換句話說,只要 Exp 這裡一列裡記錄了事務編號,就說明這條記錄被刪除了。那麼一個事務應該能看見哪些記錄呢?Postgres 裡每一個事務都會儲存一個當前系統的事務快照(Snapshot),這個快照裡會儲存事務建立時當前系統的最高(最晚)事務編號,以及目前還在進行中的事務編號。在如上圖所示的一個事務的快照裡,最高事務編號為 100,目前正在進行的事務有 25、50 和 75。對應左邊資料記錄,這 6 行資料的可見性就如同標註的一般:

  • 第一行,Cre 30,沒有刪除,在 100 這個時間點,應該能看到。
  • 第二行,Cre 50,沒有刪除,但是 50 這個事務還沒有提交,正在進行中,所以看不見。
  • 第三行,Cre 110,沒有刪除,但是 100 這個時間點 110 事務還沒有發生,所以看不見。
  • 第四行,Cre 30,Exp 80,在 80 的時候資料被刪掉了,所以看不見。
  • 第五行,Cre 30,Exp 75,在 30 的時候被建立,75 時候被刪掉了,但是 75 這個事務在 100 的時候還沒有提交,這條記錄在 100 的時候還沒有刪掉,所以看得見。
  • 第六行,Cre30,Exp 110,在 30 的時被建立,110 時候被刪掉,但是在 100 時候,110 還沒有發生,所以看得見。
綜上就是這個事務對這六條記錄的可見性,也就是一個數據版本。大家可以看一下,如果另一個事務的快照裡存的是最高事務編號為 110,正在進行的事務為 50,那麼它能看到的資料應該是哪幾行呢?
同時大家也看到,Postgres 裡刪除一行資料就是在這一行的 Exp 這個列記錄一個刪除事務的編號。相當於做了一個刪除標記,而資料沒有真正被刪除,因此 Postgres 資料庫需要定期做資料清理操作(Vacuum)。我們這裡假定所有的事務最終都是正確提交了,Postgre 在現實場景裡的可見性比介紹的要複雜,會存在某些事務沒有提交的情況,這裡不再展開。




Zilliz 以重新定義資料科學為願景,致力於打造一家全球領先的開源技術創新公司,並通過開源和雲原生解決方案為企業解鎖非結構化資料的隱藏價值。
Zilliz 構建了 Milvus 向量資料庫,以加快下一代資料平臺的發展。Milvus 資料庫是 LF AI & Data 基金會的畢業專案,能夠管理大量非結構化資料集,在新葯發現、推薦系統、聊天機器人等方面具有廣泛的應 用。
解鎖更多應用場景

本文分享自微信公眾號 - ZILLIZ(Zilliztech)。
如有侵權,請聯絡 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。