備份與還原(Backup & Restore):從 pg_dump 到 PITR 時間點恢復 | PostgreSQL

2026/07/05
備份與還原(Backup & Restore):從 pg_dump 到 PITR 時間點恢復 | PostgreSQL

備份與還原是資料庫管理員最核心的職責。PostgreSQL 提供多層次的備份策略:從輕量的 邏輯備份(pg_dump)到完整的 物理備份(pg_basebackup),再到結合 WAL 歸檔的 PITR(Point-in-Time Recovery)時間點恢復。本文將從基礎到進階,完整建立你的災難復原能力。

備份類型總覽

在開始之前,先了解 PostgreSQL 三種主要備份方式的差異:

類型代表工具優點缺點
邏輯備份pg_dump / pg_dumpall跨版本還原、可選擇性還原速度慢、不支援 PITR
物理備份pg_basebackup / pgBackRest速度快、支援 PITR只能整庫還原、不能跨主版本
儲存快照ZFS / LVM / Cloud Snapshot幾乎瞬間完成依賴底層儲存、一致性需額外處理

RPO 與 RTO 概念

選擇備份策略前,必須先定義兩個關鍵指標:

  • RPO(Recovery Point Objective)——允許損失的最大資料量(以時間衡量)。RPO = 1 小時代表最壞情況會損失 1 小時的資料
  • RTO(Recovery Time Objective)——允許的最長服務中斷時間。RTO = 15 分鐘代表必須在 15 分鐘內完成還原並上線
RPO 要求        → 備份策略
─────────────────────────────────────────────
24 小時         → 每日 pg_dump 全備即可
1 小時          → 每日全備 + 每小時 WAL 歸檔
5 分鐘          → 物理全備 + 持續 WAL Streaming
接近 0          → Streaming Replication(同步複本)

RTO 要求        → 策略
─────────────────────────────────────────────
數小時          → 從遠端儲存還原全備 + 重放 WAL
30 分鐘以內     → 備份放在本地快速儲存
5 分鐘以內      → Warm Standby(備援機即時接手)
< 1 分鐘        → Hot Standby / Patroni 自動 Failover

WAL 歸檔在備份中的角色

WAL(Write-Ahead Log)是 PostgreSQL 所有資料變更的完整記錄。物理備份只是某一時刻的靜態快照,必須搭配 WAL 才能實現 PITR。

WAL Archiving 的任務是:當 WAL segment(預設 16MB)寫滿後,在被 PostgreSQL 回收之前,複製到安全的歸檔位置:

PostgreSQL
  │
  ├── 寫入 WAL segment (pg_wal/000000010000000000000001)
  │
  ├── segment 寫滿(16MB)→ 觸發 archive_command
  │       archive_command = 'cp %p /archive/%f'
  │
  └── 確認成功(exit code = 0)後才允許回收

歸檔目錄 /archive/
    000000010000000000000001   ← 最舊
    000000010000000000000002
    000000010000000000000003   ← 最新

pg_dump — 邏輯備份

pg_dump 是最常用的邏輯備份工具,透過 MVCC 機制取得一致性快照,不需停機。

輸出格式選擇

# -Fp (plain) — 純文字 SQL,可直接閱讀,但無法並行還原
pg_dump -U postgres -Fp mydb > mydb.sql

# -Fc (custom) — 壓縮自訂格式,支援選擇性還原(推薦)
pg_dump -U postgres -Fc mydb > mydb.dump

# -Fd (directory) — 目錄格式,每個表一個檔案,支援 --jobs 並行
pg_dump -U postgres -Fd mydb -f /backup/mydb_dir/

# -Ft (tar) — tar 格式,可搭配 pg_restore
pg_dump -U postgres -Ft mydb > mydb.tar

常用參數與實務範例

# 並行 dump(只支援 -Fd 格式),-j 指定 worker 數量
pg_dump -U postgres -Fd mydb -j 4 -f /backup/mydb_dir/

# 排除特定表
pg_dump -U postgres -Fc mydb \
  --exclude-table=audit_logs \
  --exclude-table=sessions \
  > mydb_no_audit.dump

# 只備份特定 schema
pg_dump -U postgres -Fc mydb -n public -n reporting > mydb_selected.dump

# 只備份結構(不含資料)
pg_dump -U postgres -Fc --schema-only mydb > mydb_schema.dump

# 只備份資料(不含 DDL)
pg_dump -U postgres -Fc --data-only mydb > mydb_data.dump

# 排除暫存表的資料(保留結構)
pg_dump -U postgres -Fc mydb \
  --exclude-table-data='tmp_*' \
  > mydb_no_tmp_data.dump

# 設定 Lock 等待逾時,避免長時間卡住
PGPASSWORD=secret pg_dump \
  -h db.example.com -p 5432 -U postgres \
  --lock-wait-timeout=30s \
  -Fc mydb > mydb.dump

# 透過 SSH 管道直接傳輸到遠端
pg_dump -U postgres -Fc mydb | \
  ssh backup@nas "cat > /nas/backup/mydb_$(date +%Y%m%d).dump"

備份驗證

# 列出 dump 檔案的內容目錄(不解壓)
pg_restore --list mydb.dump | head -30

# 只還原結構到測試庫進行驗證
pg_restore -U postgres -d testdb --schema-only mydb.dump

pg_dumpall — 全叢集備份

pg_dumpall 備份整個 PostgreSQL cluster,包含所有資料庫、Role 和 Tablespace 定義:

# 完整全叢集備份(輸出純文字 SQL)
pg_dumpall -U postgres > cluster_full.sql

# 只備份全域物件(Role、Tablespace)— 不備份資料庫內容
pg_dumpall -U postgres --globals-only > cluster_globals.sql

# 只備份 Role 定義
pg_dumpall -U postgres --roles-only > cluster_roles.sql

# 帶壓縮的全叢集備份
pg_dumpall -U postgres | gzip > cluster_full_$(date +%Y%m%d).sql.gz

注意pg_dumpall 不支援自訂格式(-Fc),輸出固定為 plain SQL。還原時通常先用 pg_dumpall --globals-only 還原角色,再用 pg_restore 還原各個資料庫。

pg_restore — 還原邏輯備份

# 還原到已存在的資料庫
pg_restore -U postgres -d mydb mydb.dump

# 建立資料庫並還原(-C 旗標)
pg_restore -U postgres -C -d postgres mydb.dump

# 並行還原(-j),速度可大幅提升
pg_restore -U postgres -d mydb -j 4 mydb.dump

# 只還原特定表
pg_restore -U postgres -d mydb -t orders -t order_items mydb.dump

# 只還原特定 schema
pg_restore -U postgres -d mydb -n reporting mydb.dump

# 列出內容後選擇性還原(two-step)
pg_restore --list mydb.dump > restore_list.txt
# 編輯 restore_list.txt,移除不需要的項目
pg_restore -U postgres -d mydb -L restore_list.txt mydb.dump

pg_basebackup — 物理備份

pg_basebackup 透過 Streaming Replication Protocol 取得整個資料目錄的一致性快照,是 PITR 的基礎。

核心參數詳解

# 基本物理備份
pg_basebackup -U replicator -h localhost -D /backup/base/

# -X stream:同步備份 WAL(standalone 可獨立還原)
pg_basebackup -U replicator -h localhost -D /backup/base/ -X stream

# -R:自動產生 standby.signal(用於設定 Standby)
pg_basebackup -U replicator -h localhost -D /backup/standby/ -X stream -R

# -P:顯示備份進度
pg_basebackup -U replicator -h localhost -D /backup/base/ -X stream -P

# -Z:壓縮備份等級
pg_basebackup -U replicator -h localhost -D /backup/base/ -X stream -Z 9

# PG15+ 指定壓縮算法
pg_basebackup -U replicator -h localhost -D /backup/base/ \
  -X stream --compress=lz4:1

# 輸出為 tar 格式(便於儲存與傳輸)
pg_basebackup -U replicator -h localhost -Ft \
  -D /backup/base_tar/ -X stream -Z 6

完整生產級範例

# tar 壓縮 + 進度 + WAL streaming + 日誌
pg_basebackup \
  -h db.prod.example.com \
  -U replicator \
  -p 5432 \
  -D /backup/$(date +%Y%m%d_%H%M%S)/ \
  -Ft \
  -X stream \
  -Z 6 \
  -P \
  -v \
  2>&1 | tee /var/log/pg_basebackup.log

PostgreSQL 設定(允許 basebackup 連線)

-- 建立 Replication 用戶
CREATE ROLE replicator LOGIN REPLICATION PASSWORD 'replicpass';
# postgresql.conf
wal_level = replica          -- 至少要 replica(PITR 最低需求)
max_wal_senders = 3          -- 允許的 WAL sender 連線數
archive_mode = on            -- 開啟 WAL 歸檔(PITR 必要)

# pg_hba.conf
host  replication  replicator  10.0.0.0/24  scram-sha-256

pg_verifybackup — 備份驗證(PG13+)

# 驗證備份目錄(比對 backup_manifest 中的 checksums)
pg_verifybackup /backup/base/

# 跳過 checksum 驗證(速度快)
pg_verifybackup --skip-checksums /backup/base/

# 驗證 tar 格式備份
pg_verifybackup -m /backup/base_tar/backup_manifest /backup/base_tar/

PITR — 時間點恢復

PITR(Point-in-Time Recovery)允許將資料庫還原到任意歷史時間點,是物理備份 + WAL 歸檔的組合,也是 PostgreSQL 最強大的災難復原能力。

步驟一:設定 WAL 歸檔

# postgresql.conf
wal_level = replica
archive_mode = on

# 複製到本地目錄(防覆蓋寫法)
archive_command = 'test ! -f /archive/%f && cp %p /archive/%f'

# 複製到遠端(rsync)
# archive_command = 'rsync -a %p backup@nas:/pg_archive/%f'

# 上傳到 AWS S3
# archive_command = 'aws s3 cp %p s3://mybucket/pg-archive/%f'

# 強制每 N 秒歸檔(即使 segment 未寫滿),決定最大 RPO
archive_timeout = 300   -- 5 分鐘

PG15+ 新增 archive_library 可用 C extension 取代 shell command,效能更好且錯誤處理更精確。

步驟二:執行物理備份

# 使用 pg_basebackup 取得基礎備份
pg_basebackup -U replicator -h localhost -D /backup/base/ -X stream -P

步驟三:災難發生,開始還原

# 1. 停止 PostgreSQL
sudo systemctl stop postgresql

# 2. 備份當前(損壞的)資料目錄
sudo mv /var/lib/postgresql/17/main /var/lib/postgresql/17/main.broken

# 3. 還原物理備份
sudo mkdir -p /var/lib/postgresql/17/main
sudo -u postgres tar -xzf /backup/base.tar.gz -C /var/lib/postgresql/17/main/

# 4. 建立 recovery.signal(PG12+)
sudo -u postgres touch /var/lib/postgresql/17/main/recovery.signal

步驟四:設定 recovery 參數

# postgresql.conf(或 postgresql.auto.conf)

# WAL 歸檔來源
restore_command = 'cp /archive/%f %p'

# 還原目標:指定時間點(ISO 8601 格式)
recovery_target_time = '2026-07-05 14:30:00 UTC'

# 其他目標選項(擇一使用):
# recovery_target_lsn = '0/15D68C8'           -- 指定 LSN
# recovery_target_xid = '12345'                -- 指定 transaction ID
# recovery_target_name = 'before_migration_v2' -- 命名還原點

# 是否包含目標時間點的 transaction
recovery_target_inclusive = true

# 達到目標後的動作
# promote = 自動提升為 Primary(預設)
# pause   = 暫停,等待 DBA 確認
# shutdown = 完成後關閉
recovery_target_action = 'pause'

步驟五:啟動並確認恢復

# 啟動 PostgreSQL
sudo systemctl start postgresql

# 監控恢復進度
sudo tail -f /var/log/postgresql/postgresql-*.log
# 預期日誌:
# LOG: starting point-in-time recovery to 2026-07-05 14:30:00+00
# LOG: restored log file "000000010000000000000001" from archive
# LOG: consistent recovery state reached at 0/12345AB
# LOG: pausing at the end of recovery
-- 若 recovery_target_action = 'pause',確認資料正確後繼續
SELECT pg_wal_replay_resume();

-- 確認已離開恢復模式
SELECT pg_is_in_recovery();
-- 應返回 false

-- 驗證還原後的資料狀態
SELECT MAX(created_at) FROM orders;

pgBackRest — 企業級備份工具

pgBackRest 是目前最廣泛採用的 PostgreSQL 備份工具,支援增量備份、並行處理、多儲存後端(S3、GCS、Azure)。

基本設定

# /etc/pgbackrest/pgbackrest.conf

[global]
repo1-path=/var/lib/pgbackrest
repo1-retention-full=2          # 保留 2 份完整備份
repo1-retention-diff=4          # 保留 4 份差異備份
compress-type=lz4
compress-level=3
log-level-console=info

# 使用 S3 作為儲存後端
# repo1-type=s3
# repo1-s3-bucket=mybucket
# repo1-s3-region=ap-northeast-1

[main]                          # stanza 名稱
pg1-path=/var/lib/postgresql/17/main
pg1-port=5432
pg1-user=postgres
# postgresql.conf(配合 pgBackRest)
archive_command = 'pgbackrest --stanza=main archive-push %p'
archive_mode = on
wal_level = replica

日常操作

# 初始化 stanza(首次設定)
sudo -u postgres pgbackrest --stanza=main stanza-create

# 全量備份
sudo -u postgres pgbackrest --stanza=main backup --type=full

# 差異備份(自上次 full 以來的變更)
sudo -u postgres pgbackrest --stanza=main backup --type=diff

# 增量備份(自上次任何備份以來的變更)
sudo -u postgres pgbackrest --stanza=main backup --type=incr

# 並行備份(指定進程數)
sudo -u postgres pgbackrest --stanza=main backup --type=full --process-max=4

# 查看備份清單
sudo -u postgres pgbackrest --stanza=main info

# 驗證備份完整性
sudo -u postgres pgbackrest --stanza=main check

PITR 還原

# 還原到最新狀態
sudo -u postgres pgbackrest --stanza=main restore

# 還原到指定時間點
sudo -u postgres pgbackrest --stanza=main restore \
  --type=time "--target=2026-07-05 14:30:00"

# Delta restore(只更新有變更的檔案,速度更快)
sudo -u postgres pgbackrest --stanza=main restore --delta

自動化排程

# /etc/cron.d/pgbackrest

# 每週日凌晨 1 點全量備份
0 1 * * 0  postgres pgbackrest --stanza=main backup --type=full

# 週一至週六凌晨 1 點差異備份
0 1 * * 1-6 postgres pgbackrest --stanza=main backup --type=diff

# 每小時驗證歸檔狀態
0 * * * *  postgres pgbackrest --stanza=main check

三層備份策略

生產環境建議採用三層備份策略,兼顧安全性與效率:

Layer 1:全量物理備份(每週)
  ├── 工具:pgBackRest --type=full
  ├── 頻率:每週日凌晨
  ├── 保留:2 份(2 週)
  └── 目的:PITR 的起點

Layer 2:差異備份(每日)
  ├── 工具:pgBackRest --type=diff
  ├── 頻率:週一至週六凌晨
  ├── 保留:7 份
  └── 目的:縮短 WAL 重放量(縮小 RTO)

Layer 3:WAL 持續歸檔(即時)
  ├── 工具:archive_command 或 pgBackRest
  ├── 保留:至少覆蓋最舊的全量備份
  └── 目的:實現細粒度 PITR

不同業務場景的策略選擇:

場景RPORTO建議策略
小型應用24 小時4 小時每日 pg_dump + 遠端儲存
中型業務1 小時1 小時全備(週) + WAL 歸檔(5分鐘)
電商平台15 分鐘30 分鐘全備(日) + WAL(1分鐘) + Warm Standby
金融系統0 分鐘5 分鐘Streaming Replication + pgBackRest + Hot Standby

PG17 增量備份

PostgreSQL 17 正式引入內建的 Incremental Backup,是有史以來最重要的備份改進之一:

# 啟用 WAL summarizer
# postgresql.conf
summarize_wal = on

# 執行全量備份(作為增量基礎)
pg_basebackup -U replicator -D /backup/full_20260705/ -X stream

# 執行增量備份(只備份變更的 blocks)
pg_basebackup -U replicator -D /backup/incr_20260706/ \
  --incremental=/backup/full_20260705/backup_manifest \
  -X stream

# 合併增量備份為完整備份
pg_combinebackup \
  /backup/full_20260705 \
  /backup/incr_20260706 \
  -o /backup/combined_20260706/

# 驗證合併結果
pg_verifybackup /backup/combined_20260706/

PG17 增量備份的優勢:

  • 官方原生支援,不依賴第三方工具
  • pg_walsummarizer 進程追蹤修改的 block 清單
  • 增量備份大小可縮小至全備的 5~20%
  • pg_combinebackup 合併多個增量為完整備份

備份監控

-- 查看 WAL 歸檔狀態
SELECT archived_count,
       last_archived_wal,
       last_archived_time,
       failed_count,
       last_failed_wal
FROM pg_stat_archiver;

-- 計算歸檔延遲(秒)
SELECT EXTRACT(EPOCH FROM (NOW() - last_archived_time))::int
       AS archive_lag_seconds
FROM pg_stat_archiver;

-- 查看當前 WAL 位置
SELECT pg_current_wal_lsn(),
       pg_current_wal_insert_lsn();

-- 查看備份進度(若有 pg_basebackup 正在執行)
SELECT pid, application_name, state,
       sent_lsn, write_lsn, flush_lsn
FROM pg_stat_replication;

歸檔延遲告警腳本

#!/bin/bash
# 監控 WAL 歸檔延遲
MAX_LAG=600  # 10 分鐘

LAG=$(psql -U postgres -t -c "
  SELECT COALESCE(
    EXTRACT(EPOCH FROM (NOW() - last_archived_time)),
    99999
  )::int FROM pg_stat_archiver;
")

if [ "$LAG" -gt "$MAX_LAG" ]; then
  echo "CRITICAL: WAL archive lag ${LAG}s > ${MAX_LAG}s" | \
    mail -s "PostgreSQL Archive Alert" dba@example.com
fi

常見陷阱

1. archive_command 失敗導致磁碟爆滿

archive_command 失敗時,PostgreSQL 不會回收 WAL segment 但會持續重試,直到 pg_wal/ 目錄磁碟空間耗盡:

# 錯誤:目標目錄可能不存在
archive_command = 'cp %p /archive/%f'

# 正確:防覆蓋 + 確認目標存在
archive_command = 'test ! -f /archive/%f && cp %p /archive/%f'

2. pg_dump 遇到 DDL 鎖等待

# pg_dump 不鎖表,但若有並發 DDL 可能無限等待
# 設定 lock_timeout 防止卡住
PGOPTIONS="-c lock_timeout=30s" pg_dump -U postgres -Fc mydb > mydb.dump

3. 還原後 Sequences 不同步

-- 邏輯備份使用 --data-only 還原後,sequences 可能落後
-- 手動重設所有 sequences
SELECT setval(
  pg_get_serial_sequence('"' || table_name || '"', column_name),
  (SELECT MAX(id) FROM orders)
)
FROM information_schema.columns
WHERE column_default LIKE 'nextval%';

4. pg_basebackup 需要額外的 wal sender

# max_wal_senders 必須大於 現有 Standby 數 + 1
# 假設有 2 個 Standby,至少需要 3(2 standby + 1 basebackup)
max_wal_senders = 5

5. WAL 歸檔保留期不夠長

錯誤情境:
  全量備份執行於週日 → 週六需要 PITR
  但 WAL 歸檔只保留 3 天 → 週日~週三的 WAL 已刪除
  → 無法 PITR 回那段時間

正確做法:
  WAL 歸檔保留期 >= 最舊全量備份的年齡
  保留 2 份週備份 → WAL 至少保留 14 天

6. recovery.signal 的生命週期

# 完成還原後,若 recovery_target_action = 'pause'
# 必須執行 pg_wal_replay_resume() 後 PostgreSQL 才會移除 recovery.signal
# 若手動刪除 recovery.signal 再重啟,會強制離開恢復模式

7. 從 Standby 備份需注意 WAL 回收

# Standby 執行 pg_basebackup 時,Primary 可能回收所需 WAL
# 開啟 hot_standby_feedback 或使用 replication slot 防止
hot_standby_feedback = on
# 或
primary_slot_name = 'backup_slot'

版本演進

版本備份相關新功能
PG 9.1pg_basebackup 首次引入
PG 9.4Replication Slot 正式引入(防止 WAL 被提前回收)
PG 10pg_basebackup 支援 -X stream
PG 12recovery.conf 廢棄,改為 postgresql.conf + recovery.signal
PG 13pg_verifybackup 引入,backup_manifest 標準化
PG 15archive_library 引入(取代 archive_command shell);支援 zstd 壓縮
PG 16pg_basebackup 支援從 Standby 備份
PG 17內建 Incremental Backup(summarize_wal / pg_combinebackup)

總結

備份與還原是資料庫安全的最後防線。關鍵要點:

  1. 小型系統pg_dump 即可滿足基本需求
  2. 生產環境必須使用 pg_basebackup + WAL 歸檔實現 PITR
  3. 企業級建議採用 pgBackRest 的三層備份策略(全備 + 差異 + WAL)
  4. PG17 的原生增量備份是重大里程碑,未來可逐步取代第三方工具的增量功能
  5. 定期進行還原演練,確保備份真正可用——未經驗證的備份不是備份

下一篇,我們將深入探討 複寫與高可用——掌握 Streaming Replication、Logical Replication 與 Patroni 自動 Failover,打造永不停機的 PostgreSQL 叢集。

BenZ Software Developer

熱愛技術的軟體開發者,在這裡分享程式開發經驗與學習筆記。