本頁說明資料庫在 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;
這項查詢會傳回導致問題的資料表。
臨時資料表
如果 schema_name
以 pg_temp_
開頭,則解決問題的唯一方法是刪除資料表,因為 PostgreSQL 不允許您在其他工作階段中建立的臨時資料表執行 VACUUM。有時如果該工作階段已開啟且可存取,您可以在此處清除資料表,但這通常不會發生。使用下列 SQL 陳述式捨棄臨時表:
SET cloudsql.enable_maintenance_mode = 'on'; /* get extra transaction ids */
DROP TABLE pg_temp_<N>.<tablename>;
如果這是唯一的阻斷項目,Autovacuum 會在約一分鐘內擷取這項變更,並在 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 萬的差距小於或等於 1 百萬。這個值是 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
都位於 1 百萬個迴轉保護上限內。在這種情況下,您必須先移除阻礙因素,才能繼續使用 VACUUM。
移除 VACUUM 封鎖條件
長時間執行的交易
在上述查詢中,如果 oldest_running_xact
等於 oldest_prepared_xact
,請前往「
孤立的準備交易」一節,因為「最新執行中」值也包含已準備的交易。
您可能需要先以 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
或 Google 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
值為健康 (age == 59)。如果年齡接近 20 億,您應該刪除該時段。如果查詢傳回多個記錄,就沒有簡單的方法可以得知哪個副本是哪個。因此,請檢查所有這些項目,以防任何副本上有長時間執行的交易。
備援機制上的長時間交易
檢查副本,找出 hot_standby_feedback
設為 on
的最舊執行中的交易,然後在副本上停用該交易。
pg_stat_replication
檢視畫面中的 backend_xmin
欄會顯示備份資料所需的最舊 TXID
。
如要將其推進,請停止在複本上保留的查詢。如要找出造成延遲的查詢,請使用執行時間過長的交易中的查詢,但這次是在複本上執行。
另一個選項是重新啟動複本。
設定 VACUUM
設定下列兩個標記:
- autovacuum_vacuum_cost_delay = 0
- autovacuum_work_mem = 1048576
第一個選項會停用 PostgreSQL 的磁碟節流功能,讓 VACUUM 以全速執行。根據預設,autovacuum 會受到節流限制,因此不會在速度最慢的伺服器上使用所有磁碟 I/O。
第二個標記 autovacuum_work_mem
會減少索引清理通過次數。盡可能將其設為足夠大,以便在 VACUUM 要清除的資料表中儲存所有已淘汰資料列的 ID。設定這個值時,請考量這是每個執行中的 VACUUM 可配置的本地記憶體上限。請確認您允許的數量不超過可用數量,並保留一些備用數量。如果讓資料庫以唯讀模式執行,請同時考量用於唯讀查詢的本機記憶體。
在大多數系統上,請使用最大值 (1 GB 或 1048576 KB,如範例所示)。這個值最多可容納約 1 億 7800 萬個無效元組。但仍會導致多次索引掃描。
如要進一步瞭解這些旗標和其他旗標,請參閱「最佳化、監控 PostgreSQL 的 VACUUM 作業並進行疑難排解」。
設定這些旗標後,請重新啟動資料庫,讓 autovacuum 以新值啟動。
您可以使用 pg_stat_progress_vacuum
檢視畫面監控自動啟動 VACUUM 的進度。這個檢視畫面會顯示所有資料庫中執行的 VACUUM,以及其他資料庫中的資料表 (關聯),您無法使用檢視資料欄 relid
查詢資料表名稱。
如要找出下次需要進行真空處理的資料庫和資料表,請使用最佳化、監控 PostgreSQL 的 VACUUM 作業並進行疑難排解中的查詢。如果伺服器 VM 效能足夠,且有足夠頻寬可啟動比自動真空程序更多的並行真空程序,您可以啟動一些手動真空程序。
檢查真空吸塵器的速度
本節說明如何檢查 VACUUM 速度,以及如何視需要加速。
檢查正在執行的自動清理功能
所有執行 VACUUM 的後端都會顯示在系統檢視畫面中的 pg_stat_progress_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
會將調查限制在一次一個資料庫。如果這個資料庫中只有一個 autovacuum 執行中,且每個資料庫都有多個資料列,這個數量就足夠了。您必須在 WHERE 中加入額外的篩選條件 ('AND relid= …'')
,才能指明單一自動清除資料列。下一個查詢也是如此。
儲存基礎值後,您可以執行下列查詢:
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
。您可以透過Google 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% 的記憶體空間。這是每個由 autovacuum 啟動的 VACUUM 程序的上限。如果您想繼續執行唯讀工作負載,請減少記憶體用量。
其他可提高速度的方式
避免清空索引
對於大型資料表,VACUUM 會花費大部分時間清理索引。
PostgreSQL 14 提供特殊最佳化功能,可在系統有循環回風險時避免索引清理作業。
在 PostgreSQL 12 和 13 中,您可以手動執行下列陳述式:
VACUUM (INDEX_CLEANUP OFF, TRUNCATE OFF) <tablename>;
在 11 以下版本中,您可以在執行清除作業前 DROP
索引,並在之後重新建立索引。
在資料表上執行自動清除作業時,如要刪除索引,就必須取消執行中的清除作業,然後在自動清除作業再次管理該資料表的清除作業之前,立即執行刪除索引指令。
首先,請執行下列陳述式,找出需要終止的 autovacuum 程序 PID:
SELECT pid, query
FROM pg_stat_activity
WHERE state != 'idle'
AND query ilike '%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
- 進一步瞭解例行吸塵。
- 進一步瞭解自動吸塵功能
- 進一步瞭解如何 最佳化、監控 PostgreSQL 的 VACUUM 作業並進行疑難排解