1. 程式人生 > 實用技巧 >理解postgreSQL中的prepared transactions和處理孤兒(orphans)事務

理解postgreSQL中的prepared transactions和處理孤兒(orphans)事務

Prepared transactions是PostgreSQL的一個關鍵特性。理解該特性提供的功能和處理任何潛在的陷阱對於系統的維護是很關鍵的。所以,我們來深入研究一下具體什麼是prepared transactions。

關於事務

在資料庫系統中,事務是一種處理通常包含多個語句的塊中的全部或零個語句的方法。在提交整個塊之前,該塊中語句的結果對其他事務不可見。 如果事務失敗或回滾,則對資料庫完全沒有影響。

事務依附於會話。但是,當要執行與會話獨立的事務時(也有其他好處)。這就是“prepared transactions”的來源。

prepared transactions

prepared transaction是獨立於會話、抗崩潰、狀態維護的事務。事務的狀態儲存在磁碟上,這使得資料庫伺服器即使在從崩潰中重新啟動後也可以恢復事務。在對prepared transaction執行回滾或提交操作之前,將一直維護該事務。

PostgreSQL文件宣告,在一個已存在的事務塊中,可以使用prepare transaction ’transaction_id‘命令建立一個prepared transaction。它進一步宣告該過程為兩階段提交準備了一個事務。

此外,建議應用程式或互動式會話不要使用prepared transaction。理想情況下,外部事務管理器應該跨同構或異構資料庫資源執行原子的全域性事務。

在postgreSQL中,預設的max_prepared_transaction=0;即關閉了prepared transaction。如果你想使用prepared transaction,建議將max_prepared_transaction設定成max_connections的值。在同步的流複製standby庫上,最好將其設定的比max_connections大一點,以免standby不能接收查詢。

在任何給定的時間,你可以檢視活躍狀態的prepared transactions,通過檢視檢視pg_prepared_xacts。

pg_prepared_xacts檢視含有以下一些列:

#select * from pg_prepared_xacts;
 transaction | gid | prepared | owner | database 
-------------+-----+----------+-------+----------
(0 rows)

  

1.transaction:事務id

2.gid:使用者為prepared transaction定義的名稱

3.prepared:prepared日期,建立事務時帶有時區的時間戳

4.owner:建立該prepared transaction的事務

5.database:資料庫名

建立prepared transaction

知道什麼是prepared transaction之後,現在來看看如何建立一個prepared transaction。建立一個該事務通常需要四個步驟:

1.begin(或start transaction)

2.執行需要的操作

3.prepare transaction

4.commit(或rollback prepared)

prepare transaction、commit prepared、或rollback prepared後面加上一個gid,可以唯一標識prepared transaction。

例如下面的程式碼塊:

postgres=# begin;
BEGIN
postgres=# create table abce(id int);
CREATE TABLE
postgres=# insert into abce values(1);
INSERT 0 1
postgres=# prepare transaction 'abce_insert';
PREPARE TRANSACTION
postgres=# select * from pg_prepared_xacts;
 transaction |     gid     |           prepared            |  owner   | database 
-------------+-------------+-------------------------------+----------+----------
       16362 | abce_insert | 2020-12-09 11:41:45.742375+08 | postgres | postgres
(1 row)

postgres=# commit prepared 'abce_insert';
COMMIT PREPARED
postgres=# select * from pg_prepared_xacts;
 transaction | gid | prepared | owner | database 
-------------+-----+----------+-------+----------
(0 rows)

postgres=# 

  

當一個含有一個或多個活躍的prepared transactions的postgresql停止了或者奔潰了,會為每個活躍的prepared transaction建立一個檔案,在目錄pg_twophase中。

比如,我們有個prepared transaction:

postgres=# select * from pg_prepared_xacts;
 transaction |     gid      |           prepared            |  owner   | database 
-------------+--------------+-------------------------------+----------+----------
       16363 | abce_insert2 | 2020-12-09 11:46:01.983483+08 | postgres | postgres
(1 row)

postgres=# 

所以我沒有提交事務就停止了postgresql server。postgresql就會建立一個名為00003FEB的檔案,對應於prepared transaction的事務id。

$ ls -l ../data/pg_twophase/
total 4
-rw------- 1 postgres postgres 220 Dec  9 11:47 00003FEB

00003FEB等價於16363。在postgresql被重啟後,在啟動日誌會報如下資訊:

2020-12-09 11:51:28.112 CST [963] LOG:  database system was shut down at 2020-12-09 11:47:39 CST
2020-12-09 11:51:28.113 CST [963] LOG:  recovering prepared transaction 16363 from shared memory
2020-12-09 11:51:28.132 CST [960] LOG:  database system is ready to accept connections

 

如果你不希望恢復一個prepared transaction,可以簡單地刪除pg_twophase資料夾下的相應檔案。

這很簡單,不是嗎?那麼我們為什麼不經常地使用它呢?畢竟,它提供了更高的提交操作成功的可能性。事情要是這麼簡單就好了!

prepared transaction可能遇到哪些錯誤?

如果客戶端消失了,則prepared transaction可以未完成(既不提交也不回滾)。發生這種情況的原因多種多樣,包括客戶機崩潰,或者伺服器崩潰導致客戶機連線被終止而無法重新連線。你實際上是依靠事務管理器來確保沒有孤立的prepared transaction。

除了崩潰之外,還有另一種原因可以使prepared transaction未完成。如果一個用於恢復的備份包含了事務的prepared階段,但是沒有包含關閉事務的階段,仍然會生成孤兒事務。

或者,DBA建立了一個prepared transaction,卻忘記了關閉它。

所以,如果一個prepared transaction沒有完成,又會有什麼大不了的呢?

真正的問題

真正的問題是,孤兒prepared transaction繼續持有可能包含鎖的關鍵系統資源,或者使事務ID保持活動狀態,該事務ID可能會阻止vacuum清除只對該孤兒事務可見、對其它事務不可見的死的元組。

回想一下我在上面建立的prepared 事務。當事務prepared,並且在提交該事務之前,如果另一個事務試圖更改該表,它將無法獲取所需的鎖並掛起,直到解決了prepared事務(提交或回滾)為止。 否則,alter命令會無限期掛起,最終,我必須發出CTRL + C來停止該命令。

postgres=# select * from pg_prepared_xacts;
 transaction |     gid      |           prepared            |  owner   | database 
-------------+--------------+-------------------------------+----------+----------
       16363 | abce_insert2 | 2020-12-09 11:46:01.983483+08 | postgres | postgres
(1 row)

postgres=# alter table abce add column b int;
^CCancel request sent
ERROR:  canceling statement due to user request
postgres=# select c.oid,c.relname,l.locktype,l.relation,l.mode
postgres-# from pg_class c
postgres-# inner join pg_locks l on c.oid=l.relation
postgres-# where c.relname='abce';
  oid   | relname | locktype | relation |       mode       
--------+---------+----------+----------+------------------
 370883 | abce    | relation |   370883 | RowExclusiveLock
(1 row)

postgres=# 

對vacuum的阻塞可能會更嚴重,在極端情況下,會導致資料庫關閉,因為孤兒prepared事務會阻止事務id的wrap around。

發現和通知

雖然一般的預期是prepared事務在幾秒鐘內完成,但是情況並不總是這樣。一個prepared事務可能持續幾分鐘、幾小時甚至幾天。

為這些事務維護元資料本身可能是一項挑戰。但是,我建議設定一個術語來定義prepared事務可以存在的最大時間。例如,考慮以下的prepared事務:

postgres=# BEGIN;
BEGIN
postgres=# INSERT INTO abce VALUES(3);
INSERT 0 1
postgres=# PREPARE TRANSACTION 'abce_insert 1m';
PREPARE TRANSACTION

或者下面的事務:

postgres=# BEGIN;
BEGIN
postgres=# INSERT INTO abce VALUES(4);
INSERT 0 1
postgres=# PREPARE TRANSACTION 'abce_insert 1d';
PREPARE TRANSACTION

在這些事務名稱中,最後一部分定義事務的時間。任何超出時間的事務可以通過sql查詢輕易地找出來:

postgres=# select gid,prepared,regexp_replace(gid, '.* ', '') AS age
from pg_prepared_xacts
WHERE prepared + CAST(regexp_replace(gid, '.* ', '') AS INTERVAL) < NOW();
      gid       |           prepared            | age
----------------+-------------------------------+-----
 abce_insert 1m | 2020-12-09 13:39:01.383091+08 | 1m
(1 row)

postgres=#

這裡就很清晰地顯示了一個不應該再有效的事務。因此,使用一個外部代理或者cron任務可以輕易找出這些事務,或者通知管理員、或者回滾事務。

在我看來,這是一種簡單而容易的方式,可以確保即使事務管理器失敗或DBA意外地留下了一個事務,也可以在你的環境中管理孤兒事務。

結論

Prepared transactions顯然是一個非常重要的功能,但是需要使用回退通知程式或清理程式仔細設定環境,以輕鬆確保這些事務不會不必要地佔用關鍵資源,並且系統保持良好狀態。

PostgreSQL社群中仍在討論如何處理孤兒prepared事務。它是否成為postgresql核心的一部分尚待觀察。同時,我們需要使用外部工具來管理這些事務,或者設法解決這個問題。

原文地址:https://www.highgo.ca/2020/01/28/understanding-prepared-transactions-and-handling-the-orphans/