本頁說明資料庫在 PostgreSQL 中遇到交易 ID 迴繞保護機制時,您可以採取哪些行動。這會以 ERROR
訊息的形式呈現,如下所示:
database is not accepting commands to avoid wraparound data loss in database dbname. Stop the postmaster and vacuum that database in single-user mode. You might also need to commit or roll back old prepared transactions, or drop stale replication slots.
或者,系統可能會顯示如下的 WARNING
訊息:
database dbname must be vacuumed within 10985967 transactions. To avoid a database shutdown, execute a database-wide VACUUM in that database.
基本步驟說明
- 找出造成環繞的資料庫和資料表。
- 檢查是否有任何項目阻礙 (AUTO)VACUUM (例如停滯的交易 ID)。
- 測量 AUTOVACUUM 的速度。如果速度緩慢,可以視需要加快速度。
- 如有需要,請手動執行更多 VACUUM 指令。
- 請調查其他加快吸塵器運作速度的方式。有時最快的方法是捨棄資料表或部分索引。
許多旗標值建議並非精確值,這是因為這些值取決於許多資料庫參數。如要深入分析這個主題,請參閱本頁結尾的連結文件。
找出造成環繞的資料庫和資料表
尋找資料庫
如要找出包含導致環繞的資料表的資料庫,請執行下列查詢:
SELECT datname,
age(datfrozenxid),
2^31-1000000-age(datfrozenxid) as remaining
FROM pg_database
ORDER BY 3
remaining
值接近 0 的資料庫就是造成問題的原因。
尋找資料表
連線至該資料庫,然後執行下列查詢:
SELECT c.relnamespace::regnamespace as schema_name,
c.relname as table_name,
greatest(age(c.relfrozenxid),age(t.relfrozenxid)) as age,
2^31-1000000-greatest(age(c.relfrozenxid),age(t.relfrozenxid)) as remaining
FROM pg_class c
LEFT JOIN pg_class t ON c.reltoastrelid = t.oid
WHERE c.relkind IN ('r', 'm')
ORDER BY 4;
這項查詢會傳回造成問題的資料表。
適用於 TEMPORARY 資料表
如果 schema_name
以 pg_temp_
開頭,則解決問題的唯一方法是捨棄資料表,因為 PostgreSQL 不允許您 VACUUM 在其他工作階段中建立的暫時資料表。有時如果該工作階段處於開啟狀態且可供存取,您可以在該處清除資料表,但通常並非如此。使用下列 SQL 陳述式捨棄暫時資料表:
SET cloudsql.enable_maintenance_mode = 'on'; /* get extra transaction ids */
DROP TABLE pg_temp_<N>.<tablename>;
如果這是唯一的阻礙,大約一分鐘後,自動清除程序就會擷取這項變更,並在 pg_database
中將 datfrozenxid
往前移。這會解決迴繞保護機制唯讀狀態。
一般表格
如果是正常 (非暫時性) 資料表,請繼續執行下列步驟,查看是否有任何項目阻礙清除作業、VACUUM 是否執行速度夠快,以及是否正在清除最重要的資料表。
檢查交易 ID 是否卡住
系統可能用盡交易 ID 的原因之一,是 PostgreSQL 無法凍結 (也就是標示為所有交易都可見) 在目前執行中最舊交易開始後建立的任何交易 ID。這是因為多版本並行控制 (MVCC) 規則所致。在極端情況下,這類交易可能會過於老舊,導致 VACUUM 無法清除整個 20 億交易 ID 迴繞限制的任何舊交易,並導致整個系統停止接受新的 DML。記錄檔通常也會顯示警告,指出 WARNING: oldest
xmin is far in the past
。
請先修正卡住的交易 ID,再進行最佳化。
以下列出交易 ID 停滯的四個可能原因,以及如何解決各項問題:
- 長時間執行的交易:找出這類交易,然後取消或終止後端,解除真空狀態。
- 孤立的準備交易:復原這些交易。
- 已捨棄的複製運算單元:捨棄已捨棄的運算單元。
- 副本上長時間執行的交易,並具有
hot_standby_feedback = on
:找出這類交易,然後取消或終止後端,解除真空狀態。
在這些情況下,下列查詢會傳回最舊交易的年齡,以及在循環前剩餘的交易數:
WITH q AS ( SELECT (SELECT max(age(backend_xmin)) FROM pg_stat_activity WHERE state != 'idle' ) AS oldest_running_xact_age, (SELECT max(age(transaction)) FROM pg_prepared_xacts) AS oldest_prepared_xact_age, (SELECT max(greatest(age(catalog_xmin),age(xmin))) FROM pg_replication_slots) AS oldest_replication_slot_age, (SELECT max(age(backend_xmin)) FROM pg_stat_replication) AS oldest_replica_xact_age ) SELECT *, 2^31 - oldest_running_xact_age AS oldest_running_xact_left, 2^31 - oldest_prepared_xact_age AS oldest_prepared_xact_left, 2^31 - oldest_replication_slot_age AS oldest_replication_slot_left, 2^31 - oldest_replica_xact_age AS oldest_replica_xact_left FROM q;
這項查詢可能會傳回任何 *_left 值,這些值回報的距離接近或小於環繞值 100 萬。這個值是 PostgreSQL 停止接受新寫入指令時的環繞保護限制。在這種情況下,請參閱「移除 VACUUM 封鎖器」或「調整 VACUUM」。
舉例來說,上述查詢可能會傳回:
┌─[ RECORD 1 ]─────────────────┬────────────┐ │ oldest_running_xact_age │ 2146483655 │ │ oldest_prepared_xact_age │ 2146483655 │ │ oldest_replication_slot_age │ ¤ │ │ oldest_replica_xact_age │ ¤ │ │ oldest_running_xact_left │ 999993 │ │ oldest_prepared_xact_left │ 999993 │ │ oldest_replication_slot_left │ ¤ │ │ oldest_replica_xact_left │ ¤ │ └──────────────────────────────┴────────────┘
其中 oldest_running_xact_left
和 oldest_prepared_xact_left
必須在 100 萬的環繞保護限制內。在這種情況下,您必須先移除 VACUUM 的封鎖程式,才能繼續操作。
移除 VACUUM 阻斷器
長時間執行的交易
在上述查詢中,如果 oldest_running_xact
等於 oldest_prepared_xact
,請前往「Orphaned prepare transaction」(孤立的準備交易) 部分,因為「latest running」(最新執行) 值也包含準備交易。
您可能需要先以 postgres
使用者身分執行下列指令:
GRANT pg_signal_backend TO postgres;
如果違規交易屬於任何系統使用者 (開頭為 cloudsql...
),您就無法直接取消交易。如要取消,請重新啟動資料庫。
如要找出長時間執行的查詢,並取消或終止查詢來解除真空狀態,請先選取幾個最舊的查詢。LIMIT 10
線條可協助將結果調整至螢幕大小。解決最舊的執行中查詢後,您可能需要重複這個步驟。
SELECT pid, age(backend_xid) AS age_in_xids, now() - xact_start AS xact_age, now() - query_start AS query_age, state, query FROM pg_stat_activity WHERE state != 'idle' ORDER BY 2 DESC LIMIT 10;
如果 age_in_xids
回傳 NULL
,表示交易尚未獲派永久交易 ID,可以放心忽略。
取消 xids_left_to_wraparound
即將達到 100 萬的查詢。
如果 state
為 active
,則可以使用 SELECT pg_cancel_backend(pid);
取消查詢。否則,您需要使用 SELECT pg_terminate_backend(pid);
終止整個連線,其中 pid 是上一個查詢中的 pid
孤立的準備交易
列出所有準備好的交易:
DB_NAME=> SELECT age(transaction),* FROM pg_prepared_xacts ; ┌─[ RECORD 1 ]┬───────────────────────────────┐ │ age │ 2146483656 │ │ transaction │ 2455493932 │ │ gid │ trx_id_pin │ │ prepared │ 2021-03-03 16:54:07.923158+00 │ │ owner │ postgres │ │ database │ DB_NAME │ └─────────────┴───────────────────────────────┘
使用最後一個查詢中的 gid
(在本例中為 trx_id_pin
) 做為交易 ID,復原最舊的孤立預先準備交易:
ROLLBACK PREPARED trx_id_pin;
或者,提交該檔案:
COMMIT PREPARED trx_id_pin;
如需完整說明,請參閱 SQL ROLLBACK PREPARED 說明文件。
遭捨棄的複製運算單元
如果現有副本已停止、暫停或發生其他問題,導致複製運算單元遭到捨棄,您可以從 gcloud
或 Trusted Cloud 控制台刪除副本。
首先,請確認副本未停用,如「管理讀取副本」一文所述。如果副本已停用,請重新啟用。如果延遲仍偏高,請刪除備用資源,
複製位置會顯示在 pg_replication_slots
系統檢視畫面中。
下列查詢會擷取相關資訊:
SELECT *, age(xmin) AS age FROM pg_replication_slots; ┌─[ RECORD 1 ]────────┬─────────────────────────────────────────────────┐ │ slot_name │ cloudsql_1_355b114b_9ff4_4bc3_ac88_6a80199bd738 │ │ plugin │ ¤ │ │ slot_type │ physical │ │ datoid │ ¤ │ │ database │ ¤ │ │ active │ t │ │ active_pid │ 1126 │ │ xmin │ 2453745071 │ │ catalog_xmin │ ¤ │ │ restart_lsn │ C0/BEF7C2D0 │ │ confirmed_flush_lsn │ ¤ │ │ age │ 59 │ └─────────────────────┴─────────────────────────────────────────────────┘
在本範例中,pg_replication_slots
值為健康 (年齡 == 59)。如果年齡接近 20 億,您會想要刪除該時段。如果查詢傳回多筆記錄,您無法輕易判斷哪個是哪個副本。因此,請檢查所有副本,以免有任何副本上的交易執行時間過長。
副本上長時間執行的交易
使用 hot_standby_feedback
檢查最舊的執行中交易副本,並在副本上停用該交易。on
pg_stat_replication
檢視表中的 backend_xmin
資料欄含有副本上最舊的必要 TXID
。
如要繼續執行,請停止備用資源上阻礙作業的查詢。如要找出造成延遲的查詢,請在「Long running transactions」(長時間執行的交易) 中使用該查詢,但這次請在副本上執行。
你也可以選擇重新啟動副本。
設定 VACUUM
設定下列兩個標記:
- autovacuum_vacuum_cost_delay = 0
- autovacuum_work_mem = 1048576
第一個指令會停用 PostgreSQL 的任何磁碟節流,讓 VACUUM 以全速執行。根據預設,自動清除程序會受到節流,因此不會用盡最慢伺服器上的所有磁碟 IO。
第二個標記 autovacuum_work_mem
會減少索引清除作業的次數。如果可以,這個緩衝區應夠大,可儲存 VACUUM 要清除的資料表中所有已刪除資料列的 ID。設定這個值時,請注意這是每個執行的 VACUUM 可分配的本機記憶體最大量。請確認您允許的用量未超過可用量,且保留部分用量。如果資料庫持續以唯讀模式執行,請一併考量用於唯讀查詢的本機記憶體。
在大多數系統中,請使用最大值 (1 GB 或 1048576 KB,如範例所示)。 這個值最多可容納約 1.78 億個無效元組。如果超過這個數量,系統仍會多次掃描索引。
如要進一步瞭解這些和其他旗標,請參閱「最佳化、監控 PostgreSQL 的 VACUUM 作業並進行疑難排解」。
設定這些旗標後,請重新啟動資料庫,讓 autovacuum 以新值啟動。
您可以使用 pg_stat_progress_vacuum
檢視畫面,監控自動啟動的 VACUUM 的進度。這個檢視畫面會顯示所有資料庫中執行的 VACUUM,以及您無法使用檢視畫面資料欄 relid
查閱資料表名稱的其他資料庫中的資料表 (關聯)。
如要找出下一個需要 VACUUM 的資料庫和資料表,請使用「最佳化、監控 PostgreSQL 的 VACUUM 作業並進行疑難排解」中的查詢。如果伺服器 VM 的效能足夠,且頻寬可支援的平行 VACUUM 程序數量超過 autovacuum 啟動的程序,您可以手動啟動一些 VACUUM 程序。
檢查 VACUUM 速度
本節說明如何檢查 VACUUM 速度,以及如何視需要加快速度。
檢查正在執行的自動清除作業
系統檢視區塊 pg_stat_progress_vacuum 會顯示所有執行 VACUUM 的後端。
如果目前階段為 scanning heap
,您可以觀察「heap_blks_scanned
」欄的變化,監控進度。很抱歉,我們無法輕易判斷其他階段的掃描速度。
預估 VACUUM 掃描速度
如要估算掃描速度,您必須先儲存基準值,然後計算一段時間內的變化,估算完成時間。首先,您需要使用下列快照查詢,儲存 heap_blks_scanned
的快照和時間戳記:
SELECT set_config('save.ts', clock_timestamp()::text, false), set_config('save.heap_blks_scanned', heap_blks_scanned::text, false) FROM pg_stat_progress_vacuum WHERE datname = 'DB_NAME';
由於我們無法在已環繞的表格中儲存任何內容,請使用 set_config(flag, value)
將兩個使用者定義的旗標 (save.ts
和 save.heap_blks_scanned
) 設為 pg_stat_progress_vacuum
中的目前值。
在下一個查詢中,我們會將這兩者做為比較基準,判斷速度並估算完成時間。
注意:WHERE datname = DB_NAME
一次只能針對一個資料庫進行調查。如果這個資料庫中只執行一個自動清除作業,且每個資料庫有多個資料列,這個數字就足夠。額外的篩選條件 ('AND relid= …'')
需要新增至 WHERE,以指出單一自動真空列。下一個查詢也是如此。
儲存基準值後,即可執行下列查詢:
with q as ( SELECT datname, phase, heap_blks_total, heap_blks_scanned, clock_timestamp() - current_setting('save.ts')::timestamp AS ts_delta, heap_blks_scanned - current_setting('save.heap_blks_scanned')::bigint AS scanned_delta FROM pg_stat_progress_vacuum WHERE datname = DB_NAME ), q2 AS ( SELECT *, scanned_delta / extract('epoch' FROM ts_delta) AS pages_per_second FROM q ) SELECT *, (heap_blks_total - heap_blks_scanned) / pages_per_second AS remaining_time FROM q2 ;
┌─[ RECORD 1 ]──────┬──────────────────┐ │ datname │ DB_NAME │ │ phase │ scanning heap │ │ heap_blks_total │ 9497174 │ │ heap_blks_scanned │ 18016 │ │ ts_delta │ 00:00:40.30126 │ │ as_scanned_delta │ 11642 │ │ pages_per_second │ 288.87434288655 │ │ remaining_time │ 32814.1222418038 │ └───────────────────┴──────────────────┘
這項查詢會比較目前值與儲存的基準值,並計算 pages_per_second
和 remaining_time
,讓我們判斷 VACUUM 的執行速度是否夠快,或是否要加快速度。remaining_time
值僅適用於 scanning heap
階段。其他階段也需要時間,有時甚至更久。如要進一步瞭解真空,請參閱這篇文章,
並瀏覽網路上討論真空複雜層面的網誌文章。
加快 VACUUM 速度
如要加快 VACUUM 掃描速度,最簡單快速的方法是設定 autovacuum_vacuum_cost_delay=0
。您可以在Trusted Cloud 控制台中執行這項操作。
很抱歉,已執行的 VACUUM 不會擷取這個值,您可能需要重新啟動資料庫。
重新啟動後,您可能會看到類似下列的結果:
┌─[ RECORD 1 ]──────┬──────────────────┐ │ datname │ DB_NAME │ │ phase │ scanning heap │ │ heap_blks_total │ 9497174 │ │ heap_blks_scanned │ 222382 │ │ ts_delta │ 00:00:21.422615 │ │ as_scanned_delta │ 138235 │ │ pages_per_second │ 6452.76031894332 │ │ remaining_time │ 1437.33713040171 │ └───────────────────┴──────────────────┘
在本範例中,速度從每秒 <300 頁提升至每秒約 6500 頁,而堆積掃描階段的預期剩餘時間則從 9 小時縮短至 23 分鐘。
其他階段的掃描速度較難評估,但應該也會有類似的加速效果。
此外,也請盡量將 autovacuum_work_mem
設為最大值,以免多次傳遞索引。每次記憶體填滿無效元組指標時,就會發生索引傳遞。
如果資料庫沒有其他用途,請將 autovacuum_work_mem
設為在允許 shared_buffers
的必要量後,仍有約 80% 的可用記憶體。這是每個自動啟動的 VACUUM 程序上限。如要繼續執行唯讀工作負載,請減少記憶體用量。
其他提升速度的方式
避免清除索引
如果是大型資料表,VACUUM 大部分的時間會用於清理索引。
如果系統有發生迴繞的風險,PostgreSQL 14 會進行特別最佳化,避免清除索引。
在 PostgreSQL 12 和 13 中,您可以手動執行下列陳述式:
VACUUM (INDEX_CLEANUP OFF, TRUNCATE OFF) <tablename>;
在 11 之前的版本中,您可以DROP
索引,然後再重新建立。
如果自動清除作業已在資料表上執行,您必須先取消執行中的清除作業,然後立即執行 drop index 指令,才能捨棄索引,否則自動清除作業會再次開始清除該資料表。
首先,請執行下列陳述式,找出需要終止的 autovacuum 程序 PID:
SELECT pid, query
FROM pg_stat_activity
WHERE state != 'idle'
AND query ilike '%vacuum%';
然後執行下列陳述式,終止正在執行的 vacuum,並捨棄一或多個索引:
SET cloudsql.enable_maintenance_mode = 'on'; /* get extra transaction ids */
SELECT pg_terminate_backend(<pid>);DROP INDEX <index1>;DROP INDEX <index2>; ...
捨棄違規資料表
在極少數情況下,您可以捨棄表格。舉例來說,如果資料表可從備份或其他資料庫等來源輕鬆還原。
您仍需使用 cloudsql.enable_maintenance_mode = 'on'
,並可能也需要終止該資料表上的 VACUUM,如上一節所示。
集塵袋已滿
在少數情況下,執行 VACUUM FULL FREEZE
會比較快,通常是資料表只有一小部分即時元組時。您可以從 pg_stat_user_tables
檢視畫面查看這項資訊 (除非發生當機,導致統計資料遭到清除)。
VACUUM FULL
指令會將即時元組複製到新檔案,因此必須有足夠空間存放新檔案及其索引。
後續步驟
- 進一步瞭解 VACUUM for wraparound
- 進一步瞭解例行吸塵。
- 進一步瞭解自動吸塵
- 進一步瞭解如何 最佳化、監控 PostgreSQL 的 VACUUM 作業並進行疑難排解