作者:光氣
背景
ACID 事務是關係型數據庫一個重要的特性,也是 NewSQL 數據庫最大的挑戰之一。在 PolarDB-X 的架構中,Data Node(DN)是通過 Paxos 同步日誌的,保證了事務的持久性(Durability),而原子性(Atomicity)、一致性(Consistency)和隔離性(Isolation)需要通過合適的事務策略來保證。除此之外,在分佈式場景下,由於數據被分散在不同節點,線性一致性(linearizability)也是事務策略的重要特性。
目前主流的分佈式數據庫都使用了基於兩階段提交(2PC)的策略,包括 Percolator 算法和 XA 協議。Percolator 是 Google 基於 BigTable 做的分佈式系統,一個重要的設計就是使用了 Percolator 算法支持分佈式事務。在 Percolator 算法中,最重要的參與者是客戶端,更新被緩存在客戶端,並在 Commit 時通過 2PC 提交到服務端。Percolator 算法的最大優勢之一是主要狀態都在客戶端完成,服務端只需要支持簡單的 CAS 就可以,不需要維護事務狀態,也不需要引入額外的事務管理器。但 Percolator 也有一些已知缺陷:提交階段延遲較高,僅支持樂觀鎖場景、衝突錯誤只能在提交時彙報等。XA 協議全名爲 X/Open XA 協議,是一項通用的事務接口標準。XA 協議也基於兩階段提交的策略。
PolarDB-X 的事務策略
PolarDB-X 採用的是基於 XA 協議實現的分佈式事務策略,其中又分爲多種實現:
適用於 MySQL 5.6 的 BestEffort 事務
基於 InnoDB 的 XA 事務
PolarDB-X 2.0 中實現的 TSO 事務
本文會重點介紹 XA 事務和 TSO 事務。
XA 事務
在 XA 協議的設計中,有兩種角色:
事務管理器(Transaction Manager,TM):負責發起事務的提交,失敗時處理事務異常,在 PolarDB-X 中這個角色由計算節點(CN) 承擔
資源管理器(Resource Manger,RM):事務的參與方,如 MySQL 中的一個庫,在 PolarDB-X 中這個角色由存儲節點(DN)承擔
XA 協議由 TM 向多個 RM 通過 XA PREPARE 和 XA COMMIT 兩條命令完成兩階段提交。兩階段提交常常爲人詬病的問題是 TM 的單點問題和 Commit 階段發生異常可能導致的數據不一致問題。爲此,在 PolarDB-X 的實現中,我們額外引入了事務日誌表以及 COMMIT POINT 的概念。確認所有參與節點 Prepare 成功的情況下,我們向全局事務日誌添加一條事務提交記錄作爲 COMMIT POINT。在 TM 發生異常的情況下,我們可以選擇新的 TM 繼續完成兩階段提交,新 TM 會根據主庫中是否存在 COMMIT POINT 記錄選擇恢復事務狀態或者回滾事務。
如果 COMMIT POINT 不存在,那麼可以保證沒有任何一個 RM 進入 Commit Phase,此時可以安全回滾所有 RM。
如果 COMMIT POINT 存在,那麼可以保證所有 RM 都已經完成了 Prepare Phase,此時可以繼續進行 Commit Phase。
XA 事務在併發行上有一定的侷限性,在執行的過程中,我們必須使用類似 Spanner 鎖定讀寫的事務策略,爲所有 SELECT 操作帶上 LOCK IN SHARE MODE
,造成讀寫相互阻塞。基於Lock模式的分佈式事務在有單記錄併發衝突時整體性能偏低,目前數據庫業界的解法是事務多版本的MVCC策略,儘管我們的 DN 節點 InnoDB 本身支持基於 MVCC 的快照讀,但我們卻無法提供高效的快照讀事務策略,原因是事務提交時不同 DN 執行 XA COMMIT
的時間不同,一個快照讀請求請求不同分片的時間也不同,這意味着發起一個跨 DN 的快照讀請求時,可能讀到一個事務的部分已提交數據,無法得到全局的一致性視圖(Snapshot)。
TSO 事務
造成 XA 事務無法提供快照讀功能的核心原因是我們缺乏一個全局時鐘來排序每個事務,每個事務和視圖在不同的 DN 上可能是不同的順序。因此在 PolarDB-X 2.0 中,我們引入了基於 XA 事務優化的 TSO 事務。TSO 事務需要一個生成全局單調遞增 Timestamp 的策略,常見的策略有 True Time(Google Spanner)、HLC(CockroachDB)、TSO(TiDB),在我們目前的實現中,我們使用了 TSO 策略,由 GMS(元數據管理服務)作爲一個高可用的單點服務承擔生成 Timestamp 的任務。TSO 保證了正確的線性一致性和良好的性能,只是在跨全球機房部署的場景會帶來較高的延遲。
原生的 InnoDB 引擎無法滿足我們支持 TSO 事務的需求,因此我們修改了 InnoDB 引擎的提交邏輯和可見性判斷邏輯,在 XA BEGIN 和 XA COMMIT 前插入了我們自定義的兩個變量 SNAPSHOT_TS 和 COMMIT_TS。
SNAPSHOT_TS 用於判斷其他事務提交數據對當前事務的可見性,統一了一個分佈式事務在每個分片上進行讀取發生的時間。SNAPSHOT_TS 決定了當前事務的快照。
COMMIT_TS 統一了一個分佈式事務在每個分片上提交數據發生的時間,會被記錄到 InnoDB 引擎中。COMMIT_TS 決定了當前事務在全局事務中的順序。
在私有化的 InnoDB 中,我們會根據事務的 SNAPSHOT_TS 來決定可見性,同時我們也會讓新事務的讀請求在遇到處於 prepare 狀態的數據時進行等待,避免處於 prepare 狀態的事務 COMMIT_TS 比當前 SNAPSHOT_TS 更小導致 commit 前後數據不同的問題。
基於 TSO 事務,我們也做了一系列新功能和優化:
備庫一致性讀
由於 TSO 具備了全局快照的能力,我們可以將查詢的任何一部分轉發到任何分片的任何備份節點上進行讀取,並不破壞事務語義。這個 feature 對我們實現面向 HTAP 的混合執行器非常重要,TP/AP 的資源分離以及 MPP 執行框架都基於這個 feature 來保證正確性。
一階段提交優化
如果在提交階段我們發現事務只涉及了一個分片,那麼我們就會將其優化爲一階段提交,使用 XA COMMIT ONE PHASE 語句提交事務。對於正常的 TSO 事務,我們取了 SNAPSHOT_TS 和 COMMIT_TS 兩個時間戳,而對於一階段提交的事務來說,其實行爲與單機事務類似,因此我們並不需要通過 TSO 獲取 COMMIT_TS,而是可以直接由 InnoDB 計算出一個合適的 COMMIT_TS 來提交事務。具體的計算規則是:COMMIT_TS = MAX_SEQUENCE + 1,其中 MAX_SEQUENCE 爲 InnoDB 本地維護的歷史最大的 snapshot_ts。
只讀連接優化
一個事務如果使用 START TRANSACTION READ ONLY
開啓,那麼我們就會將事務標記爲只讀事務。我們會直接通過多個 autocommit 的單語句獲取需要的數據,避免長期持有連接和事務的開銷。由於 TSO 的存在,我們只需要使用相同的 TS 就能保證讀到相同的數據,因此我們通過私有協議支持在每個語句內置一個 SNAPSHOT_TS,保證了同一個事務內的多條單語句讀到相同的數據。
通常,用戶很少會主動使用 START TRANSACTION READ ONLY
開啓事務,因此對於常規事務,我們也針對每個連接使用了延遲開啓 XA 事務的策略。對於所有連接,默認以只讀的形式不開啓事務,直到第一個寫請求或者 FOR UPDATE 讀請求再進行正常的 XA 事務流程。
除了只讀事務,這個優化的另一個場景是多分片讀單分片寫的事務,通過這個方案我們能將其優化爲一階段提交事務,在 TPCC 的測試結果上得到了 14% 的性能提升。
當前讀事務優化
在上面的只讀連接優化中,我們通過將僅使用快照讀的連接摘出事務之外,來優化 COMMIT_TS 的獲取。而如果是完全相反的情況 ——— 所有連接都是寫操作或者帶鎖的當前讀操作,那麼我們完全不需要進行快照讀。因此我們也做了這樣一個優化:僅在第一次進行快照讀時獲取 SNAPSHOT_TS。這個優化針對的是一些對 Serializable 有很強需求的場景:
BEGIN;
SELECT balance FROM accounts WHERE id = 0 FOR UPDATE; # 檢查餘額,需要加鎖
UPDATE accounts SET balance = balance - 1 WHERE id = 0;
UPDATE accounts SET balance = balance + 1 WHERE id = 1;
COMMIT;
上面的 SQL 執行的是一個典型的轉賬場景,將 1 元從 id 爲 0 的賬戶轉到 id 爲 1 的賬戶,在整個事務中都沒有使用到快照讀,因此針對這種場景我們會省略 SNAPSHOT_TS 的獲取。
單機多分片優化
如果分佈式事務的多個分區位於同一個 DN 節點上時,實際上可以將它們共同視作一個單機事務。這種情況下,可以將多個 RPC 合併,一次性地完成多個分片的 PREPARE 和 COMMIT。如果所有分片均在同一個 DN 節點上,甚至可以將事務以 1PC 的方式提交。
異步提交優化
上述優化針對的依然是一部分特定場景,對於多分片的分佈式事務,往往還是相比單機事務有較長的延遲。因此我們設計了異步提交方案,針對任何分佈式事務,都可以在完成了 PREPARE 階段後直接返回成功,達到接近單機事務的提交延遲(一次跨機房 RPC)且不影響數據可靠性和線性一致性。
總結
本文主要介紹了 PolarDB-X 2.0 中的 TSO 分佈式事務的實現。相比於 PolarDB-X 1.0 (DRDS)默認的 XA 事務,TSO 事務性能更優:通過 MVCC 方式避免加讀鎖,同時通過一系列優化(例如異步提交)提升了性能。此外借助 TSO 事務還提供了備庫一致性讀的能力。