PostgreSQL中的事務隔離級別
MVCC 是 PostgreSQL 使用的并發控制技術之一。它的工作原理是在寫入操作期間創建多個版本的數據項,同時保留舊版本。當事務讀取數據項時,系統會選擇適當的版本以確保該事務的隔離。MVCC 的主要優勢在于它允許并發讀取和寫入而不會相互阻塞,另一類處理并發控制的方法是2PL(兩階段鎖)及其衍生策略。
PostgreSQL 采用了一種稱為快照隔離 (SI) 的 MVCC 變體。在 SI 中,寫入新數據項時,舊版本將在存儲中保留。然后,新版本將直接插入到相關的表格頁面中。在讀取操作期間,PostgreSQL 會應用可見性檢查規則,為每個事務選擇適當的版本。SI 可防止 ANSI SQL-92 標準中定義的三種異常,即臟讀、非重復讀取和幻讀。
PostgreSQL 使用的另一種并發控制技術是 Optimistic Concurrency Control (OCC)。與創建多個版本的數據項的 MVCC 不同,OCC 假定事務之間的沖突很少見。它允許事務在不最初獲取鎖的情況下繼續進行,但在提交階段檢查沖突。如果檢測到沖突,則會回滾其中一個沖突的事務,并且必須重試。
雖然 SI 提供了良好的隔離并防止了 三種 異常,但它無法實現真正的可序列化性。為了解決這一限制,PostgreSQL 版本引入了可序列化快照隔離 (SSI)。SSI 可以檢測序列化異常,例如 Write Skew 和 Read-only Transaction Skew,并解決由此類異常引起的沖突。借助 SSI,PostgreSQL 提供了真正的 SERIALIZABLE 隔離級別,即使在存在復雜的并發場景的情況下也能確保嚴格的一致性。
PostgreSQL 提供三個事務隔離級別:READ COMMITTED、REPEATABLE READ 和 SERIALIZABLE。這些級別決定了并發事務期間數據的可見性和行為。下表總結了 PostgreSQL 中的隔離級別及其特征。
| 隔離級別 | Dirty Reads 臟讀 | Non-repeatable Read 不可重復讀 | Phantom Read 幻讀 | Serialization Anomaly 序列化異常 |
| READ COMMITTED | 不可能 | 可能 | 可能 | 可能 |
| REPEATABLE READ | 不可能 | 不可能 | 不可能 | 可能 |
| SERIALIZABLE | 不可能 | 不可能 | 不可能 | 不可能 |
PostgreSQL 中的事務 ID (txid)
在 PostgreSQL 中,每個事務都分配有一個稱為事務 ID (xid) 的唯一標識符。它是一個 32 位無符號整數,允許大約 42 億個交易 ID。PostgreSQL 保留特殊的 xid:0 表示無效的 xid,1 表示 Bootstrap xid(在數據庫初始化期間使用),2 表示凍結的 txid。
比較事務 ID
PostgreSQL 中的事務 ID 可以相互比較。以 xid 100 為例。任何大于 100 的 xid 都被視為“將來”,從 xid 100 的角度來看是不可見的。另一方面,小于 100 的 xid 被視為“過去”,并且對 xid 100 可見。由于 xid 的空間有限,PostgreSQL 將其視為一個循環空間。之前的 21 億個 xid 被歸類為“過去”,接下來的 21 億個 xid 被歸類為“未來”。
事務 ID 回卷問題
注意 xid 回卷問題,當 xid 空間耗盡時,會發生該問題。在實際系統中,可用的 xid 空間可能不夠。當 xid 回卷問題發生時,可能會導致數據不一致和系統故障。為了緩解這個問題,PostgreSQL 采用了各種機制(如 autovacuum)來定期清理死元組并回收事務 ID。通過確保不再需要和回收舊的事務 ID,可以防止 回卷問題。
PostgreSQL 中的元組結構
在 PostgreSQL 中,表頁中的堆元組由三個主要組件組成:HeapTupleHeaderData 結構、NULL 位圖和用戶數據。就我們的目的而言,我們將重點關注四個重要系統字段:
- t_xmin:此字段保存插入元組的事務的 txid。
- t_xmax:它存儲刪除或更新元組的事務的 txid。如果元組尚未修改,則 t_xmax 設置為 0 (INVALID)。
- t_cid:命令 ID (cid) 表示同一事務中當前命令之前執行的 SQL 命令數。
- t_ctid:元組標識符 (tid) 指向自身或新元組。它用于標識表中的元組。如果 Tuples 已更新,則 t_ctid 指向新 Tuples;否則,它指向自身
插入、刪除和更新 Tuples
讓我們探索一下如何在 PostgreSQL 中插入、刪除和更新元組。我們還將介紹用于這些操作的自由空間地圖 (FSM)。
1. Insertion:插入新元組時,會直接插入到目標表的頁面中。例如,如果 Tuples 由 txid 為 99 的事務插入,則 Tuples 的 Headers 字段將相應地設置。
t_xmin:99(由 txid 99 插入)
t_xmax:0(未刪除或更新)
t_cid:0(txid 99 插入的第一個元組)
t_ctid:(0,1)(指向自身)
2. 刪除:為了邏輯刪除元組,執行 DELETE 命令的 txid 被設置為元組的 t_xmax。這會將 Tuples 標記為已刪除,并且它將成為死 Tuples。
t_xmax:111(由 txid 111 刪除)
3. Update:更新元組時,PostgreSQL 會對現有元組執行邏輯刪除并插入一個新元組。舊元組的 t_ctid 字段將更新為指向新元組。
-
第一次更新:
- Tuple_1:
- t_xmax:100(由 txid 100 邏輯刪除)
- t_ctid:(0,2)(指向新元組)
- Tuple_1:
-
第二次更新:
- Tuple_2:
- t_xmax:150(由 txid 150 邏輯刪除)
- t_ctid:(0,3)(指向新元組)
- Tuple_2:
在這兩種更新方案中,通過將原始元組的 t_xmax 值設置為相應的事務 ID 來邏輯刪除原始元組。這可確保更新的 Tuples 具有單獨的條目,并保持事務的一致性和隔離性。
Free Space Map (FSM)
Free Space Map (FSM) 是 PostgreSQL 存儲管理系統的關鍵組件。它跟蹤表的每一頁中的可用空間。FSM 在元組插入期間使用,并幫助確定適當的頁面以容納新元組。插入 Tuples 時,PostgreSQL 會查閱 FSM 以查找具有足夠可用空間的頁面。如果現有頁面沒有足夠的空間,系統會查找具有更多空間的可用頁面。找到合適的頁面后,將插入新 Tuples,并更新 FSM 以反映可用空間的變化。同樣,當刪除元組時,元組占用的空間將在 FSM 中標記為 free,從而允許將其重新用于將來的插入。
事務管理和元組可見性
事務管理和元組可見性在 PostgreSQL 中密切相關。事務 ID (txid) 在確定哪些元組對事務可見方面起著至關重要的作用。當事務開始時,它會被分配一個唯一的 txid。事務可以看到 txid 小于其自身 (過去) 的所有 Tuples。txid 大于事務 ID(將來)的元組被視為對事務不可見。此機制可確保事務隔離,因為每個事務都在數據庫的一致快照上運行,只能看到在其開始時間之前提交的 Tuples。
快照隔離和多版本并發控制 (MVCC)
PostgreSQL 采用多版本并發控制 (MVCC) 技術來實現快照隔離。MVCC 允許并發事務在數據庫上運行,而不會過度阻塞彼此。在 MVCC 下,每個事務都使用其數據庫快照,該快照由基于其事務 ID 對該事務可見的 Tuples 組成。此方法可在保持數據完整性的同時實現更高程度的并發。
事務狀態
PostgreSQL 定義了四種事務狀態:
• IN_PROGRESS:表示正在進行的事務。
• COMMITTED:表示事務已成功提交。
• ABORTED:表示已中止的事務。
• SUB_COMMITTED:保留用于子交易 。
示例:假設我們有兩個事務:
• txid 為 200 提交的事務 T1,將其狀態從 IN_PROGRESS 更改為 COMMITTED。
• txid 為 201 的事務 T2 中止,將其狀態從 IN_PROGRESS 更改為 ABORTED。
Clog 如何執行
PostgreSQL 中的提交日志 (Clog) 是存儲在共享內存中的邏輯數組。它由一個或多個 8 KB 頁面組成,其中每個頁面代表一個事務 ID (txid) 并保存相應事務的狀態。當堵塞達到其容量時,將附加一個新頁面。為了確定事務的狀態,內部函數讀取 clog 并檢索相關信息。
當 PostgreSQL 關閉時或在檢查點進程期間,clog中的數據會定期寫入存儲在 pg_xact 子目錄中的文件中。這些文件命名為 0000、0001 等,最大文件大小為 256 KB。啟動時,PostgreSQL 會加載存儲在 pg_xact 文件中的數據以初始化 clog。此外,PostgreSQL 會定期執行 vacuum 處理,以從clog中刪除不必要的舊數據。注意:在以前的版本(9.6 之前)中,pg_xact 稱為 pg_clog。
事務快照
PostgreSQL 中的事務快照是一個數據集,用于存儲有關特定時間點事務活動狀態的信息。活動事務是指正在進行或尚未開始的事務。PostgreSQL 在內部使用文本格式表示事務快照:'xmin:xmax:xip_list。例如,“100:104:100,102”表示小于 99 的 txid 未處于活動狀態,而 txid 100、102 和更大的 txid 處于活動狀態。
例如執行查詢 SELECT pg_current_snapshot();返回事務快照 '100:104:100,102':
• xmin:最早的活動 txid。
• xmax:第一個未分配的 txid。
• xip_list:xmin 和 xmax 之間的活動 txid。
可見性檢查規則
可見性檢查規則根據元組的 t_xmin、t_xmax、堵塞和獲取的事務快照來確定元組是可見的還是不可見的。讓我們關注可見性檢查的基本規則(忽略 sub-transactions 和 t_ctid 討論)。
- t_xmin的狀態為 ABORTED
- 規則 1:如果t_xmin狀態為 'ABORTED',則元組始終不可見。
- t_xmin 的狀態為 COMMITTED 且未設置 t_xmax •
- 規則 2:如果t_xmin狀態為 'COMMITTED' 且未設置 t_xmax,則元組始終可見。
- t_xmin 的狀態為 COMMITTED 且 t_xmax 已設置•
- 規則 3:如果t_xmin狀態為 'COMMITTED' 且已設置t_xmax,則如果當前事務快照早于 t_xmax,則元組可見。
- 規則 4:如果t_xmin狀態為 'COMMITTED' 且已設置t_xmax,則當當前事務快照晚于或等于 t_xmax 時,元組不可見。
- t_xmin的狀態為 IN_PROGRESS
- 規則 5:如果t_xmin狀態為“IN_PROGRESS”且未設置t_xmax,則如果當前事務快照早于啟動事務的快照 (t_xmin),則元組可見。
- 規則 6:如果t_xmin狀態為 'IN_PROGRESS' 且未設置 t_xmax,則當當前事務快照晚于或等于啟動事務的快照 (t_xmin) 時,元組將不可見。
- t_xmin 的狀態為 IN_PROGRESS 且 t_xmax 已設置
- 規則 7:如果t_xmin狀態為 'IN_PROGRESS' 且已設置t_xmax,則如果當前事務快照早于 t_xmax 且晚于或等于 t_xmin,則元組可見。
- 規則 8:如果t_xmin狀態為 'IN_PROGRESS' 且設置了 t_xmax,則當當前事務快照早于或等于 t_xmin 或晚于或等于 t_xmax 時,元組不可見。
- t_xmin 的狀態為 IN_PROGRESS 且未設置 t_xmax,并且已設置當前事務快照的 xmax
- 規則 9:如果t_xmin狀態為“IN_PROGRESS”,t_xmax未設置,并且設置了當前事務快照的 xmax,則如果當前事務快照晚于或等于 t_xmax,則元組不可見。
- t_xmin 的狀態為 IN_PROGRESS 且 t_xmax 已設置,并且當前事務快照的 xmax 已設置
- 規則 10:如果t_xmin狀態為 'IN_PROGRESS t_xmax 已設置,并且設置了當前事務快照的 xmax,則如果當前事務快照晚于 或 等于 t_xmax,則元組不可見。通過應用這些可見性檢查規則,PostgreSQL 可確保事務根據其事務狀態和其他并發事務的狀態看到一致且適當的數據。
防止異常
PostgreSQL 實施了各種措施來防止異常并確保數據一致性。讓我們看看它如何解決三個 ANSI SQL-92 異常問題:臟讀、可重復讀和幻讀。
臟讀
當事務從仍在進行的另一個事務中讀取未提交的數據時,會發生臟讀。PostgreSQL 通過遵循 Read Committed 隔離級別來防止臟讀,這可確保事務只能看到其他事務提交的更改。
Repeatable Reads 可重復讀取
Repeatable Reads 保證事務在整個執行過程中看到相同的數據快照,即使其他事務提交更改也是如此。PostgreSQL 通過使用多版本并發控制 (MVCC) 和事務 ID (txid) 來實現這一點。每個事務都有一個唯一的 txid,PostgreSQL 確保事務只看到在其快照的 txid 處可見的元組。
Phantom Reads 幻象讀取
當事務由于其他事務的并發插入或刪除而在連續讀取期間看到一組不同的行時,就會發生幻讀。PostgreSQL 通過使用 MVCC 和謂詞鎖定的組合來解決幻像讀取。
了解 PostgreSQL 中的可序列化快照隔離 (SSI)
在 PostgreSQL 中,有一個強大的隔離級別,稱為可序列化快照隔離 (SSI),可確保事務像串行運行一樣執行,即使在并發環境中也是如此。SSI 是一種健壯的機制,可以防止 Write-Skew 等序列化異常,并保證數據的一致性。在此博客中,我們將探討 PostgreSQL 中 SSI 的實現,并通過適當的示例了解它的工作原理。
結論
總之,PostgreSQL 中的并發控制在維護多用戶環境中的數據一致性和隔離性方面起著至關重要的作用。PostgreSQL 利用多版本并發控制 (MVCC)、快照隔離 (SI)、樂觀并發控制 (OCC) 和可序列化快照隔離 (SSI) 等各種技術來有效處理并發事務。
這些技術允許事務同時讀取和寫入數據而不會相互阻塞,同時確保防止臟讀、不可重復讀取和幻讀等異常情況。PostgreSQL 的事務隔離級別(包括 READ COMMITTED、REPEATABLE READ 和 SERIALIZABLE)為并發事務提供不同級別的可見性和行為。
為了管理并發,PostgreSQL 為每個事務分配一個唯一的事務 ID (txid),并實施 autovacuum 等機制來防止事務 ID 環繞問題。了解元組結構、插入、刪除和更新操作,以及自由空間映射 (FSM) 的作用,對于理解 PostgreSQL 中的并發控制至關重要。
通過有效管理事務、執行可見性檢查和利用 MVCC,PostgreSQL 即使在復雜的并發場景中也能確保數據的一致性和隔離性。這允許多個用戶同時訪問和修改數據庫,而不會影響數據的完整性。PostgreSQL 強大的并發控制機制使其成為需要并發訪問數據同時保持數據一致性和隔離性的應用程序的可靠選擇。