CMU15-445 數(shù)據(jù)庫系統(tǒng)播客:數(shù)據(jù)庫的并發(fā)控制與恢復機制(ACID與事務簡述)
數(shù)據(jù)庫的并發(fā)控制與恢復機制:為什么要有?解決什么場景的問題?
在數(shù)據(jù)庫管理系統(tǒng)(DBMS)的設計中, 并發(fā)控制(Concurrency Control)和恢復(Recovery)機制是核心且無處不在的組件 。它們貫穿整個數(shù)據(jù)庫架構(gòu),是構(gòu)建一個能夠正確運行事務并確保數(shù)據(jù)安全的數(shù)據(jù)庫系統(tǒng)的最后兩個關鍵環(huán)節(jié)。
為什么需要它們?
核心原因在于,現(xiàn)代數(shù)據(jù)庫系統(tǒng)需要支持 多用戶并發(fā)訪問和操作數(shù)據(jù) ,同時還要 應對可能發(fā)生的系統(tǒng)故障 。如果缺乏這些機制,將會出現(xiàn)嚴重的數(shù)據(jù)不一致和數(shù)據(jù)丟失問題。
并發(fā)操作導致的競態(tài)條件(Race Condition)和數(shù)據(jù)不一致:
- 場景示例: 兩個線程同時嘗試更新同一條記錄。例如,A和B各擁有1000美元,事務T1從A轉(zhuǎn)100美元到B,同時事務T2給所有賬戶增加6%的利息。
- 問題: 如果操作隨意交錯,可能會導致“ 丟失更新 (Lost Updates) ”。例如,T1取走了A的100美元,但T2在T1將錢存入B之前計算了利息,可能導致最終賬戶總額不正確,銀行憑空“丟失”了錢。
- 目標: 確保即使操作交錯,最終結(jié)果也等同于事務按某種串行順序執(zhí)行的結(jié)果。
系統(tǒng)故障導致的數(shù)據(jù)丟失和不完整:
- 場景示例: 您從您的銀行賬戶轉(zhuǎn)賬100美元到另一個賬戶,但在錢完全轉(zhuǎn)入之前,數(shù)據(jù)中心遭遇雷擊,機器崩潰或斷電。
- 問題: 當系統(tǒng)恢復時,數(shù)據(jù)庫應該處于什么狀態(tài)?錢是還在您的賬戶里,還是已經(jīng)到了對方賬戶,抑或是憑空消失了? 不完整事務導致的永久性不一致 是我們需要極力避免的。
- 目標: 確保一旦事務被提交并得到確認,即使發(fā)生系統(tǒng)故障,其所有更改也必須是 持久(Durable) 的,不會丟失。
由于這些機制的復雜性,應用程序開發(fā)者通常不應該嘗試在應用層自行實現(xiàn)它們,因為這很容易出錯,導致數(shù)據(jù)丟失或不正確。相反,應該 依賴高質(zhì)量的數(shù)據(jù)庫系統(tǒng)軟件來處理這些關鍵功能 。
事務是什么?
事務(Transaction) 是數(shù)據(jù)庫管理系統(tǒng)中的 “邏輯工作單元” 。它是一系列對數(shù)據(jù)庫的操作(例如,SQL查詢,包括讀寫操作),共同完成某個 更高層級的功能 。
數(shù)據(jù)操作邏輯上的包:
- 事務是DBMS中 “改變的基本單位” 。這意味著一個事務內(nèi)的所有操作要么全部發(fā)生并被保存,要么全部不發(fā)生,即 不允許部分事務存在 。
- 示例: 經(jīng)典的銀行轉(zhuǎn)賬例子——從賬戶A取100美元并存入賬戶B。這在高層級上是一個“轉(zhuǎn)賬”操作,但在數(shù)據(jù)庫內(nèi)部,它分解為多個低層操作:檢查余額 -> 從A扣錢 -> 向B加錢。如果中間任何一步失敗(如斷電),則整個轉(zhuǎn)賬操作都應被撤銷,就像從未發(fā)生過一樣。
SQL中的事務操作 ,即在SQL標準中,事務通過特定的關鍵字來管理:
BEGIN:顯式地開始一個新的事務。COMMIT:嘗試提交事務。如果用戶調(diào)用了COMMIT,DBMS會嘗試保存所有更改并返回成功確認。但 即使應用調(diào)用了COMMIT,數(shù)據(jù)庫系統(tǒng)也可能因為沖突等原因拒絕提交并將其終止(abort) 。ABORT(或ROLLBACK):終止事務。如果事務被終止,自BEGIN以來所做的所有更改都將被撤銷,數(shù)據(jù)庫恢復到事務開始前的狀態(tài),就好像事務從未運行過一樣。
ACID 代表什么?
ACID 是數(shù)據(jù)庫事務正確性的四個基本屬性的縮寫,是保證數(shù)據(jù)庫事務可靠性的核心概念。
A (Atomicity) - 原子性: "All or Nothing" (要么全做,要么全不做)
定義:事務中的所有操作要么作為一個整體全部成功執(zhí)行并持久化到數(shù)據(jù)庫中,要么全部不執(zhí)行,不存在中間狀態(tài)。
保障機制:
- 主流手段:日志記錄(Logging),特別是預寫日志(Write-Ahead Logging / WAL)
原理: DBMS會記錄所有對數(shù)據(jù)庫的更改,包括被覆蓋的舊值(稱為“undo records”)。這些記錄保存在內(nèi)存和磁盤上。如果事務中止或系統(tǒng)崩潰,DBMS可以使用這些記錄將數(shù)據(jù)庫回滾到事務開始前的狀態(tài),從而保證原子性。
比喻: 類似于飛機上的黑匣子,記錄了所有操作,以便在發(fā)生故障時回溯和恢復。
額外好處: 日志不僅用于恢復,還能提高性能(通過將隨機寫入轉(zhuǎn)換為順序?qū)懭耄⑻峁?nbsp;審計追蹤(Audit Trail) ,記錄了應用的所有操作,對金融等需要合規(guī)審計的行業(yè)至關重要。
WAL要點: 為了保證數(shù)據(jù)持久,必須先將日志記錄寫入磁盤,才能將對應的數(shù)據(jù)頁寫入磁盤(“先寫日志,后寫數(shù)據(jù)”)。
- 另一種方法:影子分頁(Shadow Paging)
原理: 事務不是直接修改原始數(shù)據(jù)庫文件,而是操作數(shù)據(jù)庫文件或單個頁面的副本(“影子副本”)。只有當事務成功提交時,DBMS才會將指向新副本的指針“翻轉(zhuǎn)”,使其成為新的主版本。
缺點: 這種方法非常慢且管理復雜,容易導致磁盤碎片和數(shù)據(jù)無序,因此 今天很少有系統(tǒng)使用 (僅CouchDB和LMDB等少數(shù)系統(tǒng)采用)。與多版本并發(fā)控制(MVCC)有相似之處,但MVCC通常在更細粒度(如元組)上進行復制。
C (Consistency) - 一致性: "It looks correct to me..." (看起來是對的)
定義:如果數(shù)據(jù)庫在事務開始前處于一致狀態(tài)(例如,滿足所有預定義的完整性約束),且事務本身是“一致的”(即它是一個正確的程序),那么當事務完成時,數(shù)據(jù)庫也必須保持一致狀態(tài)。
理解: 數(shù)據(jù)庫的一致性是指它 準確地反映了真實世界 ,并遵循預設的 完整性約束 。
兩個層面:
- 數(shù)據(jù)庫一致性: 由DBMS通過 完整性約束(Integrity Constraints) 來保證(例如,年齡不能小于0;外鍵引用必須存在)。DBMS會阻止違反這些約束的操作。此外,它還保證未來執(zhí)行的事務能看到過去已提交事務所做的 正確更改 。這在分布式數(shù)據(jù)庫中更為重要(強一致性 vs 最終一致性)。
- 事務一致性:這更多是應用層的責任 。DBMS無法理解應用程序的 高層級業(yè)務邏輯 或 人類的價值判斷 。例如,如果應用程序規(guī)定“修讀這門課的學生不能擁有某個賬戶”,但數(shù)據(jù)庫無法訪問學生是否選課的信息,那么即使該操作在業(yè)務邏輯上不一致,DBMS也無法阻止它。因此,事務本身的“正確性”和“一致性”由應用程序開發(fā)者負責,DBMS只能保證其原子性、隔離性和持久性。
I (Isolation) - 隔離性: "As if Alone" (如同單獨運行)
定義:并發(fā)執(zhí)行的事務之間互不干擾,每個事務都感覺自己是系統(tǒng)中唯一運行的事務, 即使其他事務同時在運行,它也應該看不到這些中間的、未提交的更改 。
重要性: 隔離性為應用程序提供了一個 更簡單的編程模型 。開發(fā)者無需擔心其他并發(fā)事務的臨時數(shù)據(jù),可以像編寫單線程代碼一樣編寫事務邏輯。
挑戰(zhàn): 盡管隔離的理想是串行執(zhí)行,但為了最大化硬件利用率、提高吞吐量和響應時間,DBMS必須 交錯執(zhí)行 多個并發(fā)事務的操作。如何在交錯操作的同時,依然維持“如同單獨運行”的錯覺,這是 并發(fā)控制協(xié)議(Concurrency Control Protocol) 需要解決的核心問題,也是 ACID中最具挑戰(zhàn)性的部分 。
區(qū)分鎖(Locks)和閂鎖(Latches):
- 閂鎖(Latches): 保護數(shù)據(jù)庫內(nèi)部數(shù)據(jù)結(jié)構(gòu)(如索引樹、哈希表)的正確性,用于同步對內(nèi)存數(shù)據(jù)結(jié)構(gòu)的訪問。
- 鎖(Locks): 保護數(shù)據(jù)庫對象(如元組、頁面、表)的正確性,用于保證事務的隔離性。鎖是流量警察,決定哪些操作可以進行,哪些必須等待或中止。
D (Durability) - 持久性: "Survive Failures" (能夠抵御故障)
定義:一旦事務成功提交并收到DBMS的確認,其所有修改都必須 永久地 保存在數(shù)據(jù)庫中,即使發(fā)生系統(tǒng)崩潰、斷電、操作系統(tǒng)崩潰等任何類型的故障,這些更改也 不會丟失 。
保障機制: 持久性主要通過 日志記錄(Logging) 來實現(xiàn)。日志記錄保證了即使內(nèi)存中的數(shù)據(jù)丟失,磁盤上的日志也能在系統(tǒng)重啟時用于恢復數(shù)據(jù)庫到最新提交的狀態(tài)。影子分頁也能提供持久性,但如前所述,其應用有限。
如何確保調(diào)度是正確的?讓并行事務效果是串行執(zhí)行的一樣
為了在允許多個事務交錯執(zhí)行的同時,仍然保持數(shù)據(jù)庫的正確性,DBMS需要一套形式化的標準來判斷一個 調(diào)度(Schedule) 是否有效。這個標準就是: 一個交錯執(zhí)行的調(diào)度,其最終結(jié)果必須等同于這些事務以某種串行順序(即一個接一個,無交錯)執(zhí)行的結(jié)果 。
- 串行調(diào)度(Serial Schedule): 不交錯不同事務操作的調(diào)度。
- 等價調(diào)度(Equivalent Schedules): 對于任何數(shù)據(jù)庫狀態(tài),執(zhí)行第一個調(diào)度的效果與執(zhí)行第二個調(diào)度的效果完全相同。
- 可串行化調(diào)度(Serializable Schedule): 與某個串行執(zhí)行等價的調(diào)度。這是并發(fā)控制的 “黃金標準” ,提供了幾乎所有能想到的保護。
沖突操作(Conflicting Operations) 是判斷調(diào)度是否可串行化的關鍵。當以下三個條件同時滿足時,兩個操作被認為是沖突的:
- 它們由 不同 的事務執(zhí)行。
- 它們操作 相同的對象 (數(shù)據(jù)項,如A或B)。
- 至少其中一個操作是 寫操作(Write) 。
(注意:讀-讀操作(Read-Read)不會沖突,因為它們不會改變數(shù)據(jù),也不會互相影響)。
并發(fā)執(zhí)行可能導致的異常(Interleaved Execution Anomalies):
- 讀-寫沖突(Read-Write Conflicts / R-W)- 不可重復讀(Unrepeatable Reads): 事務T1讀取了數(shù)據(jù)A,然后事務T2修改了A并提交,接著T1再次讀取A時,發(fā)現(xiàn)A的值變了。在T1看來,兩次讀取同一數(shù)據(jù)得到不同值,這破壞了它“單獨運行”的錯覺。
- 寫-讀沖突(Write-Read Conflicts / W-R)- 臟讀(Dirty Reads): 事務T1修改了數(shù)據(jù)A,但尚未提交。此時事務T2讀取了A的這個未提交的新值。如果隨后T1因某種原因中止(回滾),那么T2讀取到的A是一個從未真實存在過的“臟數(shù)據(jù)”。
- 寫-寫沖突(Write-Write Conflicts / W-W)- 覆蓋未提交數(shù)據(jù)/丟失更新(Overwriting Uncommitted Data/Lost Updates): 事務T1寫入數(shù)據(jù)A,但尚未提交。此時事務T2也寫入了數(shù)據(jù)A(覆蓋了T1的寫入),并且可能先于T1提交。這導致T1的寫入被覆蓋而“丟失”,或者出現(xiàn)兩個數(shù)據(jù)項被不同事務“撕裂式”更新的情況。
可串行化兩種類型
沖突可串行化(Conflict Serializability):
定義:如果一個調(diào)度可以通過 交換連續(xù)的非沖突操作 (來自不同事務的操作且不沖突)來轉(zhuǎn)換為某個串行調(diào)度,那么它就是沖突可串行化的。
判斷方法:
- 交換法: 如定義所示,通過一系列的非沖突操作交換,嘗試將調(diào)度重組為串行形式。
- 依賴圖(Dependency Graph)/前趨圖(Precedence Graph):
為調(diào)度中的每個事務創(chuàng)建一個節(jié)點。
如果事務Ti的某個操作與事務Tj的某個操作發(fā)生沖突,并且Ti的操作在調(diào)度中先于Tj的操作發(fā)生,則從Ti到Tj畫一條邊。
判斷準則:如果依賴圖是無環(huán)的(Acyclic),則該調(diào)度是沖突可串行化的;否則,它不是 。
- 實際應用: 沖突可串行化是 大多數(shù)DBMS在實際中支持的可串行化級別 (例如,SQL中的
SERIALIZABLE隔離級別)。它提供了嚴格的正確性保證,并且可以高效地實現(xiàn)。
視圖可串行化(View Serializability):
定義:一個更弱、更寬松的可串行化概念。它不僅允許所有沖突可串行化調(diào)度,還允許一些包含 盲寫(Blind Writes) (即不先讀取數(shù)據(jù)就直接寫入)的調(diào)度。
判斷標準: 兩個調(diào)度是視圖等價的,如果它們滿足特定的讀寫模式一致性(例如,如果事務T1在S1中讀取了A的初始值,那么它在S2中也必須讀取A的初始值;如果T1在S1中讀取了T2寫入的A,那么在S2中也必須如此;如果T1在S1中寫入了A的最終值,那么在S2中也必須如此)。
實際應用: 視圖可串行化雖然理論上允許更大的并發(fā)度,但 在實踐中很難高效實現(xiàn) 。因為它需要DBMS理解應用程序的 高層級語義和邏輯 (即事務的“意圖”),而DBMS通常只能看到低層級的讀寫操作。因此,它目前 僅停留在理論層面 。
兩種并發(fā)控制協(xié)議思想/方法詳細介紹
并發(fā)控制協(xié)議是DBMS決定如何交錯多個事務操作以保證隔離性的核心機制。它們可以大致分為兩類:
悲觀并發(fā)控制(Pessimistic Concurrency Control)
思想:假設事務之間會經(jīng)常發(fā)生沖突 ,因此在問題發(fā)生之前就加以預防。
實現(xiàn)方式:要求事務在執(zhí)行任何操作之前就獲取所需的鎖 。如果一個事務需要訪問某個數(shù)據(jù)對象,它必須先獲得該對象的鎖。如果該對象已經(jīng)被其他事務鎖定,當前事務就必須等待。
典型協(xié)議:兩階段鎖(Two-Phase Locking / 2PL) 。這是最廣泛使用的悲觀協(xié)議之一,將在下一次課程中詳細講解。
- 優(yōu)點: 能夠有效防止并發(fā)沖突和異常的發(fā)生,保證數(shù)據(jù)強一致性。
- 缺點:
性能影響: 頻繁的鎖請求和等待可能導致事務 停滯(stall) ,降低系統(tǒng)并行度,影響整體吞吐量和響應時間。
死鎖(Deadlock): 多個事務相互等待對方釋放鎖,導致所有事務都無法繼續(xù)執(zhí)行。DBMS需要額外的機制來檢測和解決死鎖。
樂觀并發(fā)控制(Optimistic Concurrency Control)
思想:假設事務之間的沖突是罕見的 。
實現(xiàn)方式: 允許事務在沒有任何顯式鎖的情況下自由運行。事務在本地進行所有更改。 只有在事務嘗試提交時,DBMS才會檢查是否存在沖突 。
沖突處理: 如果檢測到?jīng)_突,發(fā)生沖突的事務(通常是后提交的事務)會被 中止(abort)并回滾 ,然后由應用程序 重試 。
典型協(xié)議:基于時間戳排序(Timestamp Ordering) 。這個協(xié)議在CMU的1980年代被發(fā)明,也是下一次課程會涉及的內(nèi)容。
優(yōu)點: 在沖突率較低的環(huán)境下,可以實現(xiàn) 更高的并發(fā)度 和更好的系統(tǒng)吞吐量,因為事務不需要等待鎖。
缺點:
- 回滾開銷: 如果沖突頻繁,事務反復中止和重試的開銷會非常大,導致性能下降。
- 饑餓(Starvation): 某些事務可能因為頻繁沖突而始終無法成功提交。





































