資料庫acid裡面的d,持久化。 指的是對于使用者來說送出的事務,資料是可靠的,即使資料庫crash了,在硬體完好的情況下,也能恢複回來。
postgresql是怎麼做到的呢,看一幅圖,畫得比較醜,湊合看吧。
假設一個事務,對資料庫做了一些操作,并且産生了一些髒資料,首先這些髒資料會在資料庫的shared buffer中。
同時,産生這些髒資料的同時也會産生對應的redo資訊,産生的redo會有對應的lsn号(你可以了解為redo 的虛拟位址空間的一個唯一的offset,每一筆redo都有),這個lsn号也會記錄到shared buffer中對應的髒頁中。
walwriter是負責将wal buffer flush到持久化裝置的程序,同時它會更新一個全局變量,記錄已經flush的最大的lsn号。
bgwriter是負責将shared buffer的髒頁持久化到持久化裝置的程序,它在flush時,除了要遵循lru算法之外,還要通過lsn全局變量的比對,來保證髒頁對應的redo記錄已經flush到持久化裝置了,如果發現還對應的redo沒有持久化,會觸發wal writer去flush wal buffer。 (即確定日志比髒資料先落盤)
當使用者送出事務時,也會産生一筆送出事務的redo,這筆redo也攜帶了lsn号。backend process 同樣需要等待對應lsn flush到磁盤後才會傳回給使用者送出成功的信号。(保證日志先落盤,然後傳回給使用者)
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcuYzN4AzNkFGMjJTZxIjN0UmY2IWMlJmMjRDNyUDO4cDOmdzN1MWMiRzLcNXZslmZxl3Lc12bj5ycj5Wd5lGbh5Sdvhmen5WYo1ibj1ycz92Lc9CX6MHc0RHaiojIsJye.png)
同步流複制,即保證standby節點和本地節點的日志雙雙落盤。
postgresql使用另一組全局變量,記錄同步流複制節點已經接收到的xlog lsn,以及已經持久化的xlog lsn。
使用者在發起送出請求後,backend process除了要判斷本地wal有沒有持久化,同時還需要判斷同步流複制節點的xlog有沒有接收到或持久化(通過synchronous_commit參數控制)。
如果同步流複制節點的xlog還沒有接收或持久化,backend process會進入等待狀态。
對應的代碼和解釋如下:
committransaction @ src/backend/access/transam/xact.c
recordtransactioncommit @ src/backend/access/transam/xact.c
syncrepwaitforlsn @ src/backend/replication/syncrep.c
注意使用者進入等待狀态後,隻有主動cancel , 或者kill(terminate) , 或者主程序die才能退出無限的等待狀态。後面會講到如何将同步級别降級為異步。
前面提到了,使用者端需要等待latch的釋放信号。
那麼誰來給它這個信号了,是wal sender程序,源碼和解釋如下 :
src/backend/replication/walsender.c
syncrepreleasewaiters @ src/backend/replication/syncrep.c
syncrepwakequeue @ src/backend/replication/syncrep.c
postgresql 支援在會話中設定事務的可靠性級别。
off 表示commit 時不需要等待wal 持久化。
local 表示commit 是隻需要等待本地資料庫的wal 持久化。
remote_write 表示commit 需要等待本地資料庫的wal 持久化,同時需要等待sync standby節點wal write buffer完成(不需要持久化)。
on 表示commit 需要等待本地資料庫的wal 持久化,同時需要等待sync standby節點wal持久化。
提醒一點, synchronous_commit 的任何一種設定,都不影響wal日志持久化必須先于shared buffer髒資料持久化。 是以不管你怎麼設定,都不好影響資料的一緻性。
從前面的代碼解析可以得知,如果 backend process 進入了等待循環,隻接受幾種信号降級。 并且降級後會告警,表示本地wal已持久化,但是sync standby節點不确定wal有沒有持久化。
如果你隻配置了1個standby,并且将它配置為同步流複制節點。一旦出現網絡抖動,或者sync standby節點故障,将導緻同步事務進入等待狀态。
怎麼降級呢?
方法1.
修改配置檔案并重置
然後cancel 所有query .
收到這樣的信号,表示事務成功送出,同時表示wal不知道有沒有同步到sync standby。
同時它會讀到全局變量synchronous_commit 已經是 local了。
這樣就完成了降級的動作。
方法2.
方法1的降級需要對已有的正在等待wal sync的pid使用cancel進行處理,有點不人性化。
可以通過修改代碼的方式,做到更人性化。
syncrepwaitforlsn for循環中,加一個判斷,如果發現全局變量sync commit變成local, off了,則告警并退出。這樣就不需要人為的去cancel query了.