溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

PostgreSQL的clog—從事務回滾速度談起

發布時間:2020-08-05 05:53:49 來源:ITPUB博客 閱讀:400 作者:云和恩墨 欄目:數據庫


原文: https://www.enmotech.com/web/detail/1/701/1.html   


如果是之前學習別的數據庫的人,看PostgreSQL會感覺到有句話非常奇怪:“PostgreSQL的回滾是立即完成的,不會受到事務大小本身的影響”。

 

奇怪在哪里呢?比方我曾經遇到過一次 MySQL 的故障,一個開發給生產數據庫導入數據,用的是 Python 腳本,但是,他沒有注意一個事情, Python MySQLdb 默認情況下,是設置 autocommit 的,于是這哥們導數據(這里說的導入,不是普通那種 load data ,而是帶有業務操作的 SQL 語句,所以需要腳本操作)腳本跑了一天之后,整個數據庫的狀況就變得極為糟糕了:他導入所用的,是一個業務的核心表,一堆業務操作都需要操作這個表,但隨著這個導入動作跑了一天,占掉了大量的行鎖(幾百萬行鎖)之后,整個業務系統的對外服務都會處于一個無法求到鎖的狀況了(還摻和著 MySQL 間隙鎖的坑坑洼洼),業務服務停擺,于是,作為 DBA 來說,最終的決策,只有殺掉這個”大”事務了。一個 kill 命令過去之后,我們當時倆 DBA 開始慢慢數—小螞蟻慢慢爬——碰到—顆大豆芽——碰到兩顆大豆芽——


最終在將近三個小時的 rollback 之后,這個事務完成回滾,業務系統恢復。


所以看到 PostgreSQL 的這個描述之后,我第一時間的反應是, why ? how ? what ?


于是就有了這一篇文章,我從 PG 的事務可見性判斷講起,整理一下 PG 核心文件 clog 的機理 與作用 。


另注:從 pg 10 以后, clog 改名為 xact ,主要原因,是很多人習慣性地使用 *log 刪除日志文件,總是會不小心刪除掉原先的 xlog clog 文件,導致數據庫不可用,所以分別改名為 wal xact ,后文依然以 clog 為討論單詞,需要注意。
 
clog簡介


第一個問題,什么是 clog ?或者換個說法, PG 到底有哪些日志,它們分別是干啥的?


除了理所當前的各路文本記錄(比方數據庫的運行報錯日志之類) , PG 的二進制類日志文件主要有兩個,一個就是對應傳統數據庫理論的 redo 日志,理論上,所有數據的修改操作都會被記錄到這個日志,在事務提交的時候確保操作都記錄到磁盤中,這樣講即便發生宕機,數據庫也能以不丟數據的形態重新復活。


但是,各個數據庫在這個點上都有不同的實現,比方 MySQL 會有一個 binlog 用于跨存儲引擎的主從同步,而在 PG 中,主從同步已經通過 redo 日志( PG 術語為 XLOG )同步的情況下,為了處理沒有 undo 帶來的一系列問題,其中可見性判斷這個功能,就是交給 clog 日志文件解決的。


Clog 中記錄了每一個事務相關的 xid (記得之前曾吐槽過這個玩意的大小問題帶來的 freeze 問題)以及 xid 對應的事務的提交狀態。提交狀態包括以下一些:執行中,已提交,已中斷,已提交的子事務??吹竭@里,就可以明白,只要事務提交的時候,設置狀態為已提交,而事務回滾的時候,設置狀態為已中斷,就可以達到目的,的確避免了操作數百萬行的事務突然要回滾時候的巨大代價。


但我看到這里的時候,就產生一個疑惑,這樣的話,我查數據的時候,見到一行的 xid 之后,需要馬上確認其可見性,就需要去查 clog ,這個查詢頻率勢必極高而且隨機性很大,這個問題該怎么解決呢?

#define CLOG_BITS_PER_XACT    2

#define CLOG_XACTS_PER_BYTE 4

#define CLOG_XACTS_PER_PAGE BLCKSZ * CLOG_XACTS_PER_BYTE

#define CLOG_XACT_BITMASK    ((1 << CLOG_BITS_PER_XACT) - 1)

#define TransactionIdToPage(xid)    ((xid) / (TransactionId) CLOG_XACTS_PER_PAGE)

#define TransactionIdToPgIndex(xid) ((xid) % (TransactionId) CLOG_XACTS_PER_PAGE)

#define TransactionIdToByte(xid)    (TransactionIdToPgIndex(xid) / CLOG_XACTS_PER_BYTE)

#define TransactionIdToBIndex xid )      (( xid % TransactionId CLOG_XACTS_PER_BYTE

PG 代碼給了一個非常精彩的回答。


還記得之前 vacuum 那個里面,我大力吐槽 PG 32 xid 的執著,但這個 32 id 果真一無是處嗎?看到這里才明白,還留著這么一筆思路。


一個簡單的算術,每個事務標記占據 2 個比特位(無符號 0 1 2 3 對應前面提到的事務狀態),也就是說,每個字節可以保存 4 個事務,每當 PG 需要確定當前事務狀態的時候,就直接根據當前事務 id 計算得到對應的 clog 頁位置(除每頁 clog 之后的整數商是頁數字,而余數則是在頁中的具體位置)。真是把文件當 hash 表用的典范啊。


32 xid 的情況下,假設 xid 限制是 20 億,每個 8K clog 頁存儲 32k 事務位的情況下, clog 最大也才五百來 MB ,這部分交給操作系統的文件緩存足以保障訪問效率了。


真是一個絕妙的主意不是么?如果不考慮 64 xid 的情況下, clog 大小完全不可控的情況的話。


還是把話題集中在 clog , 下面我們來探討的是,當事務提交或者回滾的時候,其內部的運作機理又是如何呢?


以及,前文中可以看到的一個明顯問題, pg 這種操作的話,寫入的行必然是一個”執行中事務狀態”的行,這種行難道是每次查的時候,都得去找 clog 判斷嗎?如果頻繁掃他幾百萬行,是不是會有問題?
 
clog實現內部


前面提到, clog 里面會記錄的是 xid 對應的事務狀態。在 PG 里面, xid 是一個珍貴的資源(考慮到每 20 億大限的成住空壞),因此并不是每個事務都會被分配到 xid 。


一般來說,只有一個事務進行了數據修改(比如 insert , update , delete )之類的操作,才會被分配給一個 xid 。


這個事務最終提交或者回滾的時候,其最終狀態就會被記錄入 clog 。


事務提交與回滾時候的clog操作



首先來說提交。


拋開其他各種過程,每次事務提交的時候,主要的調用路徑是: CommitTransaction (提交事務時候調用)-> RecordTransactionCommit(記錄事務為已提交)-> TransactionIdCommitTree(同步標記事務為提交)/TransactionIdAsyncCommitTree(異步標記事務為提交,調用下一步需要提供lsn)-> TransactionIdSetTreeStatus(設置事務與子事務狀態)-> TransactionIdSetPageStatus(設置單數據頁內事務狀態)-> TransactionIdSetPageStatusInternal(設置實際文件頁)-> TransactionIdSetStatusBit(設置比特位)

 

其中值得拿出來講的,主要是 TransactionIdSetTreeStatus 這個方法。

 

這里涉及到一個概念,子事務。在 PG 這個地方,子事務的概念主要指:事務從開始到結束,期間可以 savepoint ,之后 rollback savepoint 而不是事務起點,在實際情況中多有應用,因此這里父事務與子事務(比如事務最終提交,但期間有回滾的情況,或者事務期間多次 save point )必須盡可能原子性的方式寫入,否則事務可見性就會出現問題。

 

在代碼注釋里面,對這里的寫入做了一個比較直觀的例子:
比如一個事務t,有子事務 t1,t2,t3,t4,其中t,t1被映射到clog頁p1,t2和t3在p2,t4在頁p3。那么寫入的時候,順序如下: 

設置p2 的t2 t3為子提交,之后設置p3的t4位子提交

設置t1為子提交,之后設置t為已提交,之后設置t1為已提交 

設置 t2 t3 為已提交,設置t4位已提交

對于回滾,實際上也是調用TransactionIdSetTreeStatus方法,只是上層函數是TransactionIdAbortTree,設置的標記是TRANSACTIONSTATUSABORTED,也就是記錄事務為中斷。語義上來說,對于事務中斷,由于事務的原子性要求,中斷的事務數據就是不可見的了,沒啥問題。

 
數據行事務可見性的判斷與clog


眾所周知的是,pg新增行都會對原先的行打一個刪除標記,然后寫在原先行的旁邊,理所當然地,每個數據行都會記錄一個事務標記(當然還有數據行對應的事務id),來確??梢娦?,避免看到事務層面已經rollback的事務。

 

首先,寫入的當時,事務沒有結束的時候,必然是”執行中”這個狀態。當事務之后提交,或者回滾的時候,pg是必然不會回頭改這個標記的,否則無論提交還是回滾,都是一個代價巨大的事情。

 

就前文所言,pg的事務可見性,是通過行的事務id,找到clog里面對應的標記位置,然后判斷的,這里非常理所當然的一個事情是,這種判斷,每一行做一次就足夠了,判斷清楚后,修改掉這個事務標記為已提交或者是中斷事務,后續讀取的時候,就不需要回查clog了。

 

PG當然就是這么干的。

 

也就是說,前一個事務所有修改的數據,它沒有在提交或者回滾的當時改掉所有的修改標記,而是把爛攤子丟給后來的人。

 

而這里還藏著一個問題:你既然修改了行的標記,那理所當然地,行所在數據塊的校驗和就變了,校驗和變了,那塊是不是就必須得傳到wal緩存走流程了?即便沒有涉及數據的變更?而且考慮到從庫查詢的時候,查數據也可以直接走從庫的clog流程,這個數據塊是不是必須傳給從庫?

 

那么,現在就有一個現成的面試問題了:PostgreSQL單純的select執行,會不會產生WAL日志?

 

事實上,這里的事務標記帶來的校驗和的問題,在PG里面的處理是比較特殊的。

 

PostgreSQL里面,當且僅當設置了walloghints或者初始化時候,initdb啟用了checksum的情況下,才會在設置標記為的時候去寫WAL日志。

 

而且這里還不是每次設置標記位都會寫。

 

必須得是,前一次checkpoint之后,數據塊第一次被修改就是sethintbit操作的情況下,才會寫整個數據塊到WAL。
 
clog的一些衍生思考



實際上就清理過期數據,MySQL也是用delete+insert替代update,但在清理以及處理上,并沒有搞到vacuum這么大代價,比如MySQL的purge線程的執行,一般很少需要特別關注,而PostgreSQL的vacuum雖然說是并行化,但是在單表內卻是串行的,民間貢獻的表內并行vacuum的補丁因為各種bug遲遲沒有合并(目前來看PG12沒戲了),這個事情為什么會這樣呢?

 

因為clog畢竟只是事務可見性的標記,而不是事務的修改關聯。在傳統的undo類實現中,修改的數據,以及關聯的事務等,都在undo按照順序存儲,purge執行的之后,直接從undo就可以找到對應的需要處理的數據塊直接處理。

 

但是對于PG來說,由于僅僅只有事務標記,vacuum必須掃描所有的數據文件的數據塊來處理這個問題,雖然pg里面,vacuum和統計信息采集合二為一(統計信息采集是傳統數據庫最大的全庫掃描行為了),但必然需要付出的全庫掃描代價卻一個都不會少。

 

因此vacuum對超大表非常慢,極端情況下在vacuum freezen時候導致全庫不可用(freezen結束前不允許執行新事務),就是有極大可能的事情了。

 

為了解決超大表,傳統建議是使用分區表,但PostgreSQL的官方實現里面,分區表一直不太穩定,并且支持不足,因此又不得不引入pathman這個外部組件來協調處理,導致運維復雜度的進一步上升,就成了理所當然的事情。

 

不過目前就PostgreSQL 12來說,已經在逐漸開放存儲引擎層面的接口,而社區中實現的undo版本的存儲引擎,雖然因為完成度問題沒有在本次release中發布,但未來可期,相信vacuum這一類問題,在未來必然會得到更好的處理。

 

PostgreSQL的clog—從事務回滾速度談起


PostgreSQL的clog—從事務回滾速度談起



想了解更多關于數據庫、云技術的內容嗎?

快來關注“數據和云”公眾號、“云和恩墨”官方網站,我們期待與大家一同學習和進步!


PostgreSQL的clog—從事務回滾速度談起

(掃描上方二維碼,關注“數據和云”公眾號,即可查看更多科技文章)


向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女