openGauss事務機制(上)

Gauss松鼠會發表於2022-11-09

事務是為使用者提供的最核心、最具吸引力的資料庫功能之一。簡單地說,事務是使用者定義的一系列資料庫操作(如查詢、插入、修改或刪除等)的集合,從資料庫內部保證了該操作集合(作為一個整體)的原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和永續性(Durability),這些特性統稱事務的 ACID特性。其中:

  • A:原子性是指事務中的所有操作要麼全部執行成功,要麼全部執行失敗。一個事務執行以後,資料庫只可能處於上述兩種狀態之一,即使資料庫在這些操作執行過程中發生故障,也不會出現只有部分操作執行成功的狀態。

  • C:一致性是指事務的執行會導致資料從一個一致的狀態轉移到另一個一致的狀態,事務的執行不會違反一致性約束、觸發器等定義的規則。

  • I:隔離性是指在事務的執行過程中,所看到的資料庫狀態受併發事務的影響程度。根據該影響程度的輕重,一般將事務的隔離級別分為讀未提交、讀已提交、可重複讀和可序列化四個級別(受併發事務影響由重到輕)。

  • D:永續性是指一旦事務提交以後,即使資料庫發生故障重啟,該事務的執行結果不會丟失,仍然對後續事務可見。

本文主要結合openGauss的事務機制和實現原理,闡述openGauss是如何保證事務的 ACID特性的。

一、openGauss事務概覽

經過前面幾篇文章的介紹,大家已經知道openGauss是一個分散式的資料庫。同樣的,openGauss的事務機制也是一個從單機到分散式的雙層構架。圖1為openGauss叢集事務元件構成示意圖。


圖1 openGauss叢集事務元件構成示意圖

如圖1所示,在openGauss叢集中,事務的執行和管理主要涉及 GTM、CN 和DN 三種元件,其中:

  • GTM(GlobalTransaction Manager,全域性事務管理器),負責全域性事務號的分發、事務提交時間戳的分發以及全域性事務執行狀態的登記。對於採用多版本併發控制(Multi-Version Concurrency Control,MVCC)的事務模型,GTM 本質上可以簡化為一個遞增序列號(或時間戳)生成器,其為叢集的所有事務進行了全域性的統一排序,以確定快照(Snapshot)內容並由此決定事務可見性。在本文openGauss併發控制中,將進一步詳述 GTM的作用。

  • CN(CoordinatorNode,協調者節點),負責管理和推進一個具體事務的執行流程,維護和推進事務執行的事務塊狀態機。

  • DN(DataNode,資料節點),負責一個具體事務在某一個資料分片內的所有讀寫操作。本文主要介紹顯式事務和隱式事務執行流程中,CN 和 DN 上事務塊狀態機的推演,以及單機事務和分散式事務的異同。

(一)顯式事務和隱式事務

顯式事務是指使用者在所執行的一條或多條 SQL後,顯式新增了開啟事務START TRANSACTION 語句和提交事務 COMMIT 語句。

隱式事務是指使用者在所執行的一條或多條 SQL 語句的前後,沒有顯式新增開啟事務和提交事務的語句。在這種情況下,每一條 SQL 語句在開始執行時,openGauss內部都會為其開啟一個事務,並且在該語句執行完成之後,自動提交該事務。

以一條SELECT 語句和一條INSERT 語句為例,簡要描述顯式事務和隱式事務在openGauss叢集中的主要執行流程。

顯式事務的SQL語句如下(假設表t只包含一個整數型別欄位a,且為分佈列):

START TRANSACTION;
SELECT * FROM t;
INSERT INTO t(a) VALUES (100);
COMMIT;

(1)START TRANSACTION

該SQL語句只在 CN 上執行,CN 顯式開啟一個事務,並將 CN 本地事務塊狀態機從空閒狀態設定為進行中狀態,然後返回客戶端,等待下一條SQL命令。

(2)SELECT * FROM t

該SQL語句首先在 CN 上執行,由於openGauss分片採用一致性雜湊演算法,因此對於不帶分佈列上謂詞條件的查詢語句,CN 需要將該SQL語句傳送到所有 DN 上執行。對於每一個分片對應的 DN,由於採用了顯式事務,CN 會先傳送一條 START TRANSACTION 命令給該 DN,讓該 DN 顯式開啟事務(DN 上的事務塊狀態機從空閒狀態變為進行中狀態),然後 CN 將 SELECT 語句傳送給該 DN。此後,CN 在收到所有DN的查詢結果之後,返回客戶端,等待下一條SQL命令。

(3)INSERTINTOt(a)VALUES(100)

該SQL語句首先在 CN 上執行,由於a為表t的分佈列,因此 CN 可以根據被插入記錄中a的具體取值,決定應該由哪個資料分片對應的 DN 執行實際的插入操作(這裡 假 設 該 分 片 為 DN1)。由於採用了顯式事務,CN先傳送一條 START TRANSACTION 命令給 DN1,由於經過第(2)步,DN1的事務塊狀態機已經處於進行中狀態,因此 對 於 該 語 句,DN1 並 不 會 執 行 什 麼 實 際 的 操 作,然 後,CN將具體的INSERT 語句傳送給 DN1,並等待 DN1執行插入成功之後,返回客戶端,等待下一條SQL命令。

(4)COMMIT

該SQL語句首先在CN 上執行,CN 進入提交事務階段後,將COMMIT 語句傳送給所有參與第(2)步和第(3)步的 DN,讓這些 DN 結束該事務,並將 DN 本地的事務塊狀態機從進行中狀態置為空閒狀態。CN 在收到所有 DN 的事務提交結果之後,再將CN 本地的事務塊狀態機從進行中狀態置為空閒狀態。然後,CN 返回客戶端,該事務執行完成。

上述操作的隱式事務語句如下(假設表t只包含一個整數型別欄位 a,且為分佈列):

SELECT * FROM t;
INSERT INTO t(a) VALUES (1);

(1)SELECT * FROM t

該SQL語句首先在 CN 上執行,CN 隱式開啟一個事務,將 CN 本地的事務塊狀態機從空閒狀態置為開啟狀態(注意不同於顯式事務中的進行中狀態)。然後,CN 需要將該語句傳送到所有 DN 上執行。對於每一個分片對應的 DN,由於採用了隱式事務且該語句為只讀查詢,CN 直接將SELECT 語句傳送給該 DN。

DN收到該SELECT語句之後,亦採用隱式事務:第一步,隱式開啟事務,將DN本地的事務塊狀態機從空閒狀態置為開啟狀態;第二步,執行該查詢語句,將查詢結果返回給CN;第三步,隱式提交事務,將 DN本地的事務塊狀態機從開啟狀態置為空閒狀態。CN 在收到所有 DN 的查詢結果之後,返回客戶端,並隱式提交事務,將 CN 本地的事務塊狀態機從開啟狀態置為空閒狀態。

(2)INSERT INTO t(a) VALUES(1)

該SQL語句首先在 CN 上執行,CN 隱式開啟一個事務,將 CN 本地的事務塊狀態機從空閒狀態置為開啟狀態。然後,CN 需要將該INSERT 語句傳送到目的分片的DN 上執行(這裡假設該分片為 DN1)。

雖然該語句採用了隱式事務,但是由於該語句為寫操作,因此在 DN1上會採取顯式事務:CN 會先傳送一條START TRANSACTION 命令給 DN1,讓 DN1顯式開啟事務(DN1上的事務塊狀態機從空閒狀態變為進行中狀態),然後 CN 將INSERT語句傳送給 DN1,DN1執行完成後,返回執行結果給 CN。

CN 收到執行結果之後,進入提交事務階段。先傳送COMMIT語句到DN1。DN1收到 COMMIT 語句後,進行顯式提交,將 DN1本地的事務塊狀態機從進行中狀態置為空閒狀態。CN 在收到 DN1的事務提交結果之後,本地再進行隱式提交事務, 將 CN 本地的事務塊狀態機從開啟狀態置為空閒狀態,返回客戶端,該事務執行完成。

綜上,對於CN 來說,使用顯式事務還是隱式事務,完全取決於使用者輸入的SQL語句;對於 DN 來說,只有當 SQL為隱式只讀事務時,才會使用隱式事務,當 SQL為顯式事務或者隱式寫事務時,都會使用顯式事務。

(二) 單機事務和分散式事務

在openGauss這樣的分散式叢集中,單機事務(亦稱單分片事務)是指一個事務中所有的操作都發生在同一個分片(即 DN)上,分散式事務是指一個事務中有兩個或以上的分片參與了該事務的執行。

對於單機事務,其寫操作的原子性和讀操作的一致性由該 DN 自身的事務機制就能保證;對於分散式事務,不同分片之間寫操作的原子性和不同分片之間讀操作的一致性,需要額外的機制來保障。下面結合 SQL 語句簡要介紹分散式事務的原子性和一致性要求,具體的原理機制在openGauss分散式事務中說明。

首先,考慮涉及多分片的寫操作事務,以如下事務 T1為例(假設表t只包含一個整數型別欄位a,且為分佈列):

START TRANSACTION;
INSERT INTO t(a) VALUES (v1);
INSERT INTO t(a) VALUES (v2);
COMMIT;

上面事務 T1的兩條INSERT 語句均為只涉及一個分片的寫(插入)事務,如果v1和v2分佈在同一個分片內,那麼該事務為單機事務,如果v1和v2分佈在兩個不同的分片內,那麼該事務為分散式事務。

對於只涉及一個 DN 分片的單機事務,其對於資料庫的修改和影響全部發生在同一個分片內,因此該分片的事務提交結果即是該事務在整個叢集的提交結果,該分片事務提交的原子性就能夠保證整個事務的原子性。在事務 T1示例中,如果v1和v2全分佈在 DN1上,那麼在 DN1上,如果事務提交,那麼這兩條記錄就全部插入成功; 如果 DN1上事務回滾,那麼這兩條記錄的插入就全部失敗。

對於分散式事務,為了保證事務在整個叢集範圍內的原子性,必須保證所有參與圖2 分散式事務原子性問題示意圖寫操作的分片要麼全部提交,要麼全部回滾,不能出現部分分片提 交,部 分 分 片 回 滾 的 “中 間態”。如圖2所示,如果 v1插入到 DN1上,且 DN1提交成功,同時,v2插入到 DN2上,且DN2最終回滾,那麼最終該事務只有一部分操作成功,破壞了事務的原子性要求。為了避免這種情況的發生,openGauss採用兩階段提交(Two Phase Commit,2PC)協議,來保證分散式事務的原子性,在後面內容中會對兩階段提交相關內容進行更詳細的介紹。


圖2 分散式事務原子性問題示意圖

其次,考慮涉及多分片的讀操作事務 T2,以如下 SQL 語句為例(假設表t只包含一個整數型別欄位a,且為分佈列):

START TRANSACTION;
SELECT * FROM t where a = v1 or a = v2;
COMMIT;

上面查詢事務 T2中,如果 v1和 v2分佈在同一個分片內,那麼該事務為單機事務,如果v1和v2分佈在兩個不同的分片內,那麼該事務為分散式事務。對於單機事務,其查詢的資料完全來自於同一個分片內,因此該分片事務的可見性和一致性就能夠保證整個事務的一致性。

在事務 T1和 T2示例中,考慮 T1和 T2併發執行的場景(假設 T1提交成功),如果v1和v2全分佈在DN1上,那麼,在DN1上,如果 T1對 T2可見,那麼 T2就能查詢到所有的兩條記錄,如果 T1對 T2不可見,那麼 T2不會查詢到兩條記錄中的任何一條。

對於分散式事務,其查詢的資料來自不同的分片,單個分片的可見性和一致性無法完全保證整個事務的一致性,不同分片之間事務提交的先後順序和可見性判斷會導致查詢結果存在某種“不確定性”。

仍考慮 T1和 T2併發執行的場景(假設 T1提交成功)。如圖3所示,如果v1和v2分別分佈在 DN1和 DN2上,若在 DN1上,T1事務提交先於 T2的查詢執行,且對於 T2可見,而在 DN2上,T2的查詢執行先於 T1事務提交(或 T1事務提交先於T2查詢執行,但對 T2不可見),那麼 T2最終只會查詢到v1這一條記錄。對於以銀行為代表的傳統資料庫使用者來說,這種現象破壞了事務作為一個整體的一致性要求。在分散式事務中,亦稱為強一致性要求。

另一方面,如果 T1先完成提交,並等待足夠長的時間以後(保證所有分片均完成T1的提交,並保證提交結果對 T2可見),再執行 T2,那麼 T2將可以看到 T1插入的所有兩條記錄。在分散式事務中,這種一致性表現被稱為最終一致性。與傳統資料庫使用者不同,在網際網路等新興業務中,最終一致性是被廣泛接受的。

openGauss透過全域性一致性的時間戳(快照)技術和本地兩階段事務補償技術),提供分散式強一致事務的能力,同時,對於追求效能的新興資料庫業務,也支援可選的最終一致性事務的能力。


圖3 分散式事務一致性問題示意圖

二 openGauss事務 ACID特性介紹

本文主要介紹openGauss中如何保證單機事務的 ACID特性,在此基礎上,在後面內容將說明如何保證分散式事務的 ACID特性。

(一)openGauss中的事務永續性

和業界幾乎所有的資料庫一樣,openGauss透過將事務對於資料庫的修改寫入可永久(長時間)儲存的儲存介質中,來保證事務的永續性。這個過程稱為事務的持久化過程。持久化過程是保證事務永續性所必不可少的環節,其效率對於資料庫整體效能影響很大,常常成為資料庫的效能瓶頸所在。

最常用的儲存介質是磁碟。對於磁碟來說,其每次讀寫操作都有一個“啟動”代價,因此在單位時間內(每秒內),一個磁碟可以進行的讀寫操作次數(Input/Output Operations Per Second,IOPS)是有上限的。HDD 磁碟的IOPS一般在1000次/秒以下,SSD磁碟的IOPS可以達到10000次/秒。另一方面,如果多個磁碟讀寫請求的資料在磁碟上是相鄰的,那麼可以被合併為一次讀寫操作,這導致磁碟順序讀寫的效能通常要遠優於隨機讀寫。

一般來說,尤其是在 OLTP場景下,使用者對於資料庫資料的修改是比較分散隨機的。如果在持久化過程中,直接將這些分散的資料寫入磁碟,那麼這個隨機寫入的效能是比較差的。因此,資料庫通常都採用 WAL(WriteAheadLog,預寫日誌)來避免持久化過程中的隨機IO,如圖10-4(a)所示。所謂預寫日誌,是指在事務提交的時候, 先將事務對於資料庫的修改寫入一個順序追加的 WAL檔案中。由於 WAL的寫操作是順序IO,因此其可以達到一個比較高的效能。另一方面,對於真正修改的物理資料檔案,再等待合適的時機寫入磁碟,以儘可能合併該資料檔案上的IO 操作。


(a) WAL日誌和資料頁面的關係示意圖

(b)WAL日誌和故障恢復示意圖

圖4 WAL和事務永續性示意圖

在一個事務完成日誌的下盤操作(即寫入磁碟)以後,該事務就可以完成提交動作。如果在此之後資料庫發生當機,那麼資料庫會首先從已經寫入磁碟的 WAL 檔案中恢復出該事務對於資料庫的修改操作,從而保證事務一旦提交即具備永續性的特點。

下面結合圖4(b)中的例子,簡單說明資料庫故障恢復的原理。假設一個事務需要在表 A(對應資料檔案 A)和表 B(對應資料檔案 B)中各插入一行新記錄,在資料庫內部,其執行的順序如下:①記錄修改資料檔案 A 的日誌;②記錄修改資料檔案 B的日誌;③在資料檔案 A 中寫入新記錄;④在資料檔案 B中寫入新記錄。在上述過程中,如果在第④步執行時資料庫發生當機,那麼該事務對於資料檔案 B的修改可能全部或部分丟失。當資料庫再次啟動以後,在其能夠接受新的業務之前,需要將這些可能丟失的修改從日誌中找回來(該操作被稱為日誌回放操作)。

在日誌回放過程中,資料庫會根據日誌記錄的先後順序,依次讀取每個日誌的內容,然後判斷該日誌記錄的事務對資料庫資料檔案的修改是否和當前相關資料檔案的內容一致。如果一致,說明上次資料庫停機之前修改已經寫入資料檔案中,該日誌修改無須回放;如果不一致,說明上次資料庫停機之前修改還未寫入資料檔案中,上次資料庫停機可能是異常當機導致,該日誌對應的事務操作需要重新在相關資料檔案中再次執行,才能保證恢復成功。

對於本例,在資料庫恢復過程中,首先讀取到在資料檔案 A 中插入記錄的日誌,將資料檔案 A 讀取上來之後,發現資料檔案 A 中已經包含該記錄,因此該日誌無須回放;然後讀取到在資料檔案 B中插入記錄的日誌,將資料檔案 B讀取上來之後,發現資料檔案 B中未包含新插入的記錄,因此需要將日誌中的記錄再次寫入到資料檔案 B中,從而完成恢復。最終,該事務對於資料庫所有的修改都得以恢復出來,事務的永續性得到了保證。

(二) openGauss中的事務原子性

如圖5所示,openGauss透過 WAL、事務提交資訊日誌以及更新記錄的多版本來保證寫事務的原子性。

圖5 openGauss事務的原子性示意圖

(1)插入事務是原子性的,例如以下插入事務:

START TRANSACTION;
INSERT INTO t(a) VALUES (v1);
INSERT INTO t(a) VALUES (v2);
COMMIT;

通常將一條記錄在資料庫內部的物理組織方式稱為元組,其在形式上類似一個結構體。在上述插入事務的執行過程中,對於每一條新插入的記錄,在它們元組結構體頭部的xmin 成 員 處都附加了插入事務的唯一標識,即一個全域性遞增的事務號(Transaction ID,XID)。如上文所述,這兩條插入的記錄(元組)連同它們的頭部會被順序寫入WAL中。

在該事務的提交階段,在 WAL中,會插入一條事務提交日誌,以持久化該事務的提交結果,並會在專門的 CLOG(Commit LOG,事務提交資訊日誌)中記錄該事務號對應的事務提交結果(提交還是回滾)。此後,如果有查詢事務讀到這兩條記錄,會首先去 CLOG 中查詢記錄頭部事務號對應的提交資訊,如果為提交,並且透過可見性判斷,那麼這兩條記錄會在查詢結果中返回;如果 CLOG 中事務號為回滾狀態,或者CLOG 中事務號為提交狀態但是該事務號對該查詢不可見,那麼這兩條記錄不會在查詢結果中返回。如上,在沒有故障發生的情況下,上述插入兩行記錄的事務是原子的,不會發生只看到插入一條記錄的“中間狀態”。

下面考慮故障場景。

  • 如果在事務寫下提交日誌之前,資料庫發生當機,那麼資料庫恢復過程中雖然會把這兩條記錄插入到資料頁面中,但是並不會在CLOG 中將該插入事務號標識為提交狀態,後續查詢也不會返回這兩條記錄。

  • 如果在事務寫下提交日誌之後,資料庫發生當機,那麼資料庫恢復過程中,不僅會把這兩條記錄插入到資料頁面中。同時,還會在CLOG 中將該插入事務號標識為提交狀態,後續查詢可以同時看見這兩條插入的記錄。如上,在故障場景下,上述插入兩行記錄的事務操作亦是原子性的。

(2)刪除事務是原子性的,例如:

START TRANSACTION;
DELETE FROM t WHERE a = v1;
DELETE FROM t WHERE a = v2;
COMMIT;

在該刪除事務的執行過程中,對於上面每一條被刪除的記錄,在它們元組頭部的xmax成員處都附加了刪除事務的事務號。同時,與插入操作相同,該刪除事務的提交狀態透過事務提交日誌物化,並記錄到 CLOG 中。從而,無論在正常場景還是故障場景下,如果後續查詢涉及上述被刪除的那些記錄,它們的可見性均取決於統一的、在CLOG 中記錄的刪除事務的狀態,不會發生部分記錄能查詢到、部分記錄不能查詢到的“中間狀態”。

(3)更新事務是原子性的,例如:

START TRANSACTION;
UPDATE t set a = v1’ WHERE a = v1;
UPDATE t set a = v2’ WHERE a = v2;COMMIT;

在openGauss中,上述更新事務等同於先刪除v1和v2這兩行老版本記錄,再插入v1’和v2’這兩行新版本記錄,刪除和插入事務的原子性已經在上面說明,因此更新事務亦是原子性的。

(三)openGauss中的事務一致性

在圖3分散式事務一致性問題示意圖中,對於併發執行的事務,如果沒有一種機制來保障,那麼其中的讀事務,可能會只讀到併發寫事務的部分資料。事實上,對於併發的單機事務,也可能存在類似的現象。

仍考慮圖3的例子,只是插入事務 T1和查詢事務 T2都發生在同一個 DN 上。如圖6所示,首先 T1在表t中插入v1和v2兩條記錄,在其提交之前,查詢事務 T2 開始執行。在 T2順序掃描表t的過程中,首先掃描到v1記錄,但是由於此時v1記錄的xmin對應的 XID1(T1的事務號)還沒有提交,因此v1不可見。然後 T1完成提交, T2繼續掃描,並掃描到v2記錄,此時v2記錄的xmin對應的 XID1已經提交,因此v2可見。這樣,查詢事務 T2只看到了 T1的部分插入資料,破壞了事務的一致性要求。

圖6 單機事務一致性問題示意圖

為了解決上面這個問題,openGauss採用 MVCC(多版本併發控制)機制來保證與寫事務併發執行的查詢事務的一致性。

MVCC的基本機制是:寫事務不會原地修改元組內容,而是將被修改的元組標記為這條記錄的一箇舊版本(標記xmax),同時插入一條修改後的元組,從而產生這條記錄的一個新版本;對於在一個查詢事務開始時還沒有提交的寫事務,那麼這個查詢事務始終認為該寫事務沒有提交。

在上面的例子中,在 T2開始的時候,T1還沒有提交,那麼對於 T2掃描上來的v1和v2記錄,T2會認為它們xmin對應的 XID1均為未提交的,即這兩個新版本對於 T1均不可見,因此不會返回任何一條記錄,也就不會發生讀到部分事務內容的異常情況了。

在 MVCC中,最關鍵的技術點有兩個:①元組版本號的實現;②快照的實現。下面詳細說明這兩個技術點在openGauss中的實現,在後面內容中將結合具體示例說明基於 MVCC機制的讀-寫併發控制實現方式。

在openGauss中,採用全域性遞增的事務號來作為一個元組的版本號,每個寫事務都會獲得一個新的事務號。如上所述,一個元組的頭部會記錄兩個事務號 xmin和xmax,分別對應元組的插入事務和刪除(更新)事務。xmin和 xmax決定了元組的命期,亦即該版本的可見性視窗。

相比之下,快照的實現要更為複雜。在openGauss中,有兩種方式來實現快照。

(1)活躍事務陣列方法。

在資料庫程式中,維護一個全域性的陣列,其中的成員為正在執行的事務資訊,包括事務的事務號,該陣列即活躍事務陣列。在每個事務開始的時候,複製一份該陣列內容。當事務執行過程中掃描到某個元組時,需要透過判斷元組xmin和xmax這兩個事務(即元組的插入事務和刪除事務)對於查詢事務的可見性,來決定該元組是否對查詢事務可見。以xmin為例,首先查詢CLOG,判斷該事務是否提交,如果未提交,則不可見;如果提交,則進一步判斷該xmin是否在查詢事務的活躍事務陣列中。如果xmin在該陣列中,或者xmin的值大於該陣列中事務號的最大值(事務號是全域性遞增發放的),那麼該xmin事務一定在該查詢事務開始之後才會提交,因此對於查詢事務不可見;如果xmin不在該陣列中,或者小於該陣列中事務號的最小值,那麼該xmin事務一定在該查詢事務開始之前就已經提交,因此對於查詢事務可見。上述判斷邏輯如圖7所示。

圖7 基於活躍事務陣列方法的事務可見性判斷示意圖

判斷元組xmax事務對查詢事務的可見性與此類似。最終,xmin(元組的插入事務事務號)和xmax(元組的刪除事務事務號)的不同組合,決定了該元組是否對於查詢事務可見,如表1所示。

表1 事務可見性判斷

xmax狀態/xmin狀態 xmax對於查詢可見 xmax對於查詢不可見
xmin對於查詢可見 記錄不可見(先插入,後刪除) 記錄可見(先插入,未刪除)
xmin對於查詢不可見 不可能發生 記錄不可見(未插入,未刪除)

(2)時間戳方法。

使用活躍事務陣列方法,由於該陣列一般比較大,無法使用原子操作,因此在其上的讀-寫併發操作需要加鎖互斥,寫-寫併發操作亦需要加鎖互斥。其中,讀操作是指事務開始時複製陣列內容獲取快照的操作,寫操作是指事務開始時將事務資訊加入到該陣列中以及事務結束時將事務資訊從該陣列中移除的操作。在高併發的場景下,活躍事務陣列會成為加鎖的熱點和效能瓶頸。

獲取快照,本質上是要獲取事務執行狀態與時間的對映關係f(t)。對每一個事務來說,該f(t)函式為一個階梯函式,如圖8所示,在該事務的提交時刻點tcommit 之前,f(t)為未提交狀態,在tcommit 之後,f(t)為提交狀態。

圖8 事務執行狀態與時間函式關係的示意圖

由此,某一個事務 T的快照內容,即是其他所有事務Tother 的事務狀態函式fother(t) 在該事務開始時刻點tstart 的取值狀態。根據fother的定義,可知,若tstart <= ,則該事務Tother在T的快照中為未提交狀態,其對資料庫的寫操作對事務T不可見;若tstart > ,則該事務Tother在T的快照中為提交狀態,其對資料庫的寫操作對事務T可見。

在openGauss內部,使用一個全域性自增的長整數作為邏輯的時間戳,模擬資料庫內部的時序,該邏輯時間戳被稱為提交順序號(Commit Sequence Number,簡稱CSN)。每當一個事務提交的時候,在提交序列號日誌中(CommitSequenceNumberLog,CSN日誌)會記錄該事務號 XID(事務的全域性唯一標識)對應的邏輯時間戳 CSN 值。CSN日誌中記錄的 XID值與 CSN 值的對應關係,即決定了所有事務的狀態函式f(t)。

如圖9所示,在一個事務的實際執行過程中,並不會在一開始就載入全部的CSN 日誌,而是在掃描到某條記錄以後,才會去 CSN 日誌中查詢該條記錄頭部 xmin和xmax這兩個事務號對應的 CSN 值,並基於此進行可見性判斷。

圖9基於時間戳方法的事務可見性判斷示意圖

(四) openGauss中的事務隔離性

在上文中,事務的一致性反映的是某一個事務在其他併發事務“眼中”的狀態。本節要介紹的事務隔離性,是某一個事務在執行過程中,它“眼中”其他所有併發事務的狀態。一致性和隔離性,兩者相互聯絡,在openGauss中兩者均是基於MVCC和快照實現的;同時,兩者又有一定區別,對於較高的隔離級別,除了 MVCC和快照之外,還需要輔以其他的機制來實現。

如表2所示,在資料庫業界,一般按由低到高將隔離性分為以下四個隔離級別: 讀未提交、讀已提交、可重複讀、可序列化。每個隔離級別按照在該級別下禁止發生的異常現象來定義。這些異常現象包括:

  • 髒讀,指一個事務在執行過程中讀到併發的、還沒有提交的寫事務的修改內容。

  • 不可重複讀,指在同一個事務內,先後兩次讀到的同一條記錄的內容發生了變化(被併發的寫事務修改)。

  • (3)幻讀,指在同一個事務內,先後兩次執行的、謂詞條件相同的範圍查詢,返回的結果不同(併發寫事務插入了新記錄)。

表2 事務隔離級別

隔離級別 髒讀 不可重複讀 幻讀
讀未提交 允許 允許 允許
讀已提交 不允許 允許 允許
可重複讀 不允許 不允許 允許
可序列化 不允許 不允許 不允許

隔離級別越高,在一個事務執行過程中,它能“感知”到的併發事務的影響越小。在最高的可序列化隔離級別下,任意一個事務的執行,均“感知”不到有任何其他併發事務執行的影響,並且所有事務執行的效果就和一個個順序執行的效果完全相同。

在openGauss中,隔離級別的實現基於 MVCC和快照機制,因此這種隔離方式被稱為 快 照 隔 離 (Snapshot Isolation,SI)。目 前,openGauss 支 持 讀 已 提 交 (Read Committed)和可重複讀(Repeatable Read)這兩種隔離級別。兩者實現上的差別在於在一個事務中獲取快照的次數。

如果採用讀已提交的隔離級別,那麼在一個事務塊中每條語句的執行開始階段,都會去獲取一次最新的快照,從而可以看到那些在本事務塊開始以後、在前面語句執行過程中提交的併發事務的效果。如果採用可重複讀的隔離級別,那麼在一個事務塊中,只會在第一條語句的執行開始階段,獲取一次快照,後面執行的所有語句都會採用這個快照,整個事務塊中的所有語句均不會看到該快照之後提交的併發事務的效果。

下面透過具體的例子說明讀已提交和可重複讀的隔離級別的區別。

考慮三個併發執行的事務(表t包含一個整型欄位a):

T1:

START TRANSACTION;
INSERT INTO t VALUES (v1);
COMMIT;

T2:

START TRANSACTION;
INSERT INTO t VALUES (v2);
COMMIT;

T3:

START TRANSACTION;
SELECT * FROM t;
SELECT * FROM t;
SELECT * FROM t;COMMIT;

這三個事務的併發執行順序如圖10所示。考慮 T3事務三條查詢的返回結果。如果採用“讀已提交”的隔離級別,那麼在第一條查詢開始時,首次獲取快照,T1和T2均沒有提交,因此它們都在快照中,查詢結果不會包含它們插入的新記錄;在第二條查詢開始時,第二次獲取快照,T1已經提交,在第二條查詢語句的快照中,只有T2,因此可以查詢到 T1插入的記錄 v1;同理,在第三條查詢開始時,第三次獲取快照,T1和 T2均已經提交,它們都不在第三條語句的快照中,因此可以查詢到它們插入的記錄v1和v2。

圖10 讀已提交和可重複讀隔離級別在併發事務下的表現區別

另一方面,如果採用可重複讀的隔離級別,對於 T3中的三條查詢語句,均會採用第一條語句執行開始時的快照,而 T1和 T2均在該快照中,因此在該隔離級別下,T3的三條查詢語句均不會返回v1和v2。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69997967/viewspace-2922537/,如需轉載,請註明出處,否則將追究法律責任。

相關文章