在任何一個涉及數據存儲與訪問的系統中,保證數據的準確性和一致性都是最核心的需求。想象一下銀行轉賬的場景:A向B轉賬100元。這個操作至少包含兩個步驟:從A的賬户扣款100元,向B的賬户增加100元。如果扣款成功後,系統突然出現狀況,導致B的賬户未能增加金額,其結果將是災難性的。
數據庫事務,正是為了解決這類問題而誕生的關鍵機制。它確保了即使在併發操作和潛在系統故障的情況下,一系列數據庫操作也能像單個邏輯單元一樣被可靠地執行。
一、 事務的基石:ACID原則
事務的價值通過其四個核心屬性來體現,即廣為人知的ACID原則:
- 原子性: 事務中的所有操作,要麼全部成功完成,要麼全部不執行。不存在中間狀態。這通常通過數據庫的回滾日誌 來實現,一旦事務失敗,可以利用回滾日誌將數據恢復到事務開始前的狀態。
- 一致性: 事務必須使數據庫從一個一致性狀態變換到另一個一致性狀態。這意味着事務的執行不會破壞預定義的業務規則和數據完整性約束(如外鍵、唯一性約束)。這是原子性、隔離性和持久性共同追求的目標。
- 隔離性: 併發執行的事務之間應該相互隔離,防止它們互相干擾。一個事務的中間狀態不應被其他併發事務看到。這正是隔離級別 要詳細定義和解決的問題。
- 持久性: 一旦事務提交,它對數據的修改就是永久性的,即使發生系統故障(如斷電),數據也不會丟失。這通常通過重做日誌 來實現,數據庫在恢復時可以利用這些日誌重新執行已提交的事務。
二、 併發下的數據挑戰:為什麼需要隔離?
當多個事務同時訪問相同的數據時,如果沒有任何隔離措施,就會引發一系列異常情況。SQL標準主要定義了以下幾種:
- 髒讀: 一個事務讀到了另一個未提交事務 修改的數據。如果那個事務之後被回滾,那麼此事務讀到的數據就是無效的。
- 場景: 事務A將某條記錄的餘額從100元改為200元(尚未提交)。此時事務B讀取了這條記錄,看到餘額是200元,並基於此進行了後續操作。隨後事務A因故回滾,餘額恢復為100元,但事務B使用的200元已經是“髒”數據。
- 不可重複讀: 在同一個事務內,兩次讀取同一條記錄,得到了不同的結果。這是因為在兩次讀取之間,另一個已提交事務 修改了該數據。
- 場景: 事務A第一次讀取餘額為100元。此時事務B提交了更新,將餘額改為200元。事務A再次讀取餘額,發現變成了200元。在同一個事務內,對同一數據的讀取結果不一致。
- 幻讀: 在同一個事務內,兩次執行相同的查詢,返回的記錄集合不同。這是因為在兩次查詢之間,另一個已提交事務 插入或刪除了符合查詢條件的記錄。
- 場景: 事務A查詢年齡小於30歲的用户,返回了10條記錄。此時事務B插入了一個新的25歲用户並提交。事務A再次執行相同的查詢,返回了11條記錄。對事務A而言,這多出來的一條記錄就像“幻覺”一樣。
不可重複讀與幻讀的核心區別在於: 不可重複讀針對的是已存在的某條數據的值 被修改,而幻讀針對的是符合條件的記錄數量 因增刪而發生變化。
三、 SQL標準的四種隔離級別
為了在數據一致性和併發性能之間取得平衡,SQL標準定義了四種隔離級別,從寬鬆到嚴格依次為:
- 讀未提交: 允許事務讀取其他未提交事務修改的數據。這是最低的級別,無法避免髒讀、不可重複讀和幻讀。性能最高,但數據一致性最差,實踐中極少使用。
- 讀已提交: 一個事務只能讀取到其他已提交事務 修改的數據。這是許多數據庫(如Oracle、PostgreSQL)的默認級別。
- 解決了: 髒讀。
- 未解決: 不可重複讀、幻讀。
- 可重複讀: 確保在同一個事務中,多次讀取同一數據的結果是一致的。即使有其他已提交事務修改了該數據,也不會被看到。
- 解決了: 髒讀、不可重複讀。
- 未解決: 幻讀(在部分數據庫如MySQL的InnoDB引擎中,通過併發控制機制在此級別也避免了幻讀)。
- 可序列化: 最嚴格的隔離級別。它要求事務串行化執行,而不是併發執行。它通過強制事務排序,使其不可能相互衝突。
- 解決了: 髒讀、不可重複讀、幻讀。
- 代價: 性能最低,因為大量的鎖和併發控制會導致等待和超時。
各級別與異常現象的對照表:
|
隔離級別
|
髒讀
|
不可重複讀
|
幻讀
|
|
讀未提交
|
可能
|
可能
|
可能
|
|
讀已提交
|
不可能 |
可能
|
可能
|
|
可重複讀
|
不可能 |
不可能 |
可能
|
|
可序列化
|
不可能 |
不可能 |
不可能 |
四、 實現機制淺析:鎖與多版本併發控制
數據庫如何實現這些隔離級別?主要有兩種主流技術:
- 基於鎖的併發控制:
- 共享鎖: 用於讀操作。多個事務可以同時持有同一數據的共享鎖。
- 排他鎖: 用於寫操作。一個事務持有排他鎖後,其他事務無法再獲取共享鎖或排他鎖。
- 機制: 通過在不同粒度(行、表)上加鎖,以及鎖的持有時間(到語句結束還是事務結束)來實現不同級別的隔離。缺點是容易引發鎖等待,甚至死鎖。
- 多版本併發控制(MVCC):
- 這是現代數據庫(如PostgreSQL, MySQL InnoDB, Oracle)更常用的技術。
- 核心思想: 為每一行數據維護多個版本。當一個事務開始時,數據庫會為其生成一個“快照”。在該事務的整個生命週期內,它看到的都是這個快照版本的數據,從而天然地實現了“讀不阻塞寫,寫不阻塞讀”,並避免了不可重複讀。
- 優勢: 極大地提高了讀併發性能,避免了大量的鎖競爭。