Foreign Data Wrapper(FDW):跨資料庫查詢的瑞士刀 | PostgreSQL

2026/07/13
Foreign Data Wrapper(FDW):跨資料庫查詢的瑞士刀 | PostgreSQL

PostgreSQLForeign Data Wrapper(FDW) 實作了 SQL/MED 標準,讓你透過標準 SQL 語法查詢遠端 PostgreSQL、MySQL、Oracle、CSV 檔案甚至 REST API——如同操作本地表格一般。本文從 FDW 三層架構到 Pushdown 優化原理,完整解析這個跨資料庫整合的利器。

什麼是 FDW?

Foreign Data Wrapper(外部資料封裝器)是 PostgreSQL 實作 SQL/MED(Management of External Data,ISO/IEC 9075-9)標準的機制。它將外部資料源的連線、協議轉換、查詢優化等複雜性封裝在擴展層,讓應用程式只需操作 Foreign Table 即可存取各種異質資料源。

PostgreSQL 自 9.1 版起提供完整的 SQL/MED 實作,使其能夠作為資料整合中心(Data Integration Hub),統一存取多個外部資料源。

FDW 三層架構

FDW 由三個相互依存的資料庫物件組成,形成完整的連線與存取鏈:

┌─────────────────────────────────────────────────────┐
│                    客戶端查詢                          │
│    SELECT * FROM orders o JOIN remote.users u ...    │
└────────────────────────┬────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────┐
│             Foreign Table(外部表格)                   │
│  定義欄位結構,指向 Foreign Server 上的遠端物件           │
└────────────────────────┬────────────────────────────┘
                         │ 使用哪個伺服器?
                         ▼
┌─────────────────────────────────────────────────────┐
│             Foreign Server(外部伺服器)                │
│  描述遠端資料源的位址、連接埠、資料庫名稱                  │
└────────────────────────┬────────────────────────────┘
                         │ 用哪個身份連線?
                         ▼
┌─────────────────────────────────────────────────────┐
│             User Mapping(使用者映射)                  │
│  將本地使用者身份映射到遠端的認證憑證                      │
└─────────────────────────────────────────────────────┘
  • Foreign Data Wrapper:定義存取特定類型資料源的驅動程式(如 postgres_fdwfile_fdw
  • Foreign Server:代表一個外部資料源實例,儲存主機、連接埠等連線資訊
  • User Mapping:將本地使用者映射至遠端的認證憑證(帳號、密碼)
  • Foreign Table:本地可見的虛擬表格,定義欄位結構並指向 Foreign Server

Pushdown 優化機制

Pushdown(下推) 是 FDW 最重要的效能優化機制。核心思想是將 SQL 操作(WHERE 過濾、JOIN、排序、彙總)推送至遠端執行,減少網路傳輸的資料量。

沒有 Pushdown:把整張遠端表格拉回來,本地再過濾 → 傳輸 100 萬筆,只用 100 筆

有 Pushdown:遠端先過濾,只傳回結果 → 只傳輸 100 筆,效能大幅提升

postgres_fdw 支援的 Pushdown 類型(截至 PostgreSQL 16):

Pushdown 類型說明支援版本
WHERE Pushdown過濾條件在遠端執行9.1+
JOIN Pushdown兩張 Foreign Table 的 JOIN 在遠端執行14+
Sort PushdownORDER BY 在遠端執行14+
Aggregate PushdownCOUNT/SUM/AVG 等彙總在遠端執行11+
LIMIT/OFFSET Pushdown分頁限制在遠端執行14+

postgres_fdw 完整設定流程

postgres_fdw 是 PostgreSQL 官方提供的 FDW,專門用於存取遠端 PostgreSQL 資料庫。

步驟一:安裝擴展

-- 安裝 postgres_fdw 擴展
CREATE EXTENSION IF NOT EXISTS postgres_fdw;

-- 確認安裝成功
SELECT * FROM pg_extension WHERE extname = 'postgres_fdw';

步驟二:建立 Foreign Server

-- 建立指向遠端 PostgreSQL 實例的 Foreign Server
CREATE SERVER remote_prod
  FOREIGN DATA WRAPPER postgres_fdw
  OPTIONS (
    host '10.0.1.100',       -- 遠端主機位址
    port '5432',              -- 遠端連接埠
    dbname 'production',      -- 遠端資料庫名稱
    connect_timeout '10',     -- 連線逾時(秒)
    keepalives '1',           -- 啟用 TCP keepalive
    keepalives_idle '60'      -- keepalive 閒置時間
  );

-- 查看已建立的 Foreign Server
SELECT * FROM pg_foreign_server;

步驟三:建立 User Mapping

-- 為特定本地使用者建立遠端認證映射
CREATE USER MAPPING FOR app_user
  SERVER remote_prod
  OPTIONS (
    user 'remote_reader',
    password 'secure_password_here'
  );

-- 為 DBA 建立管理員映射
CREATE USER MAPPING FOR postgres
  SERVER remote_prod
  OPTIONS (
    user 'remote_admin',
    password 'admin_password'
  );

步驟四:建立 Foreign Table

方法 A:手動建立

-- 手動定義 Foreign Table(需與遠端表格結構對應)
CREATE FOREIGN TABLE remote_users (
  id          BIGINT        NOT NULL,
  username    VARCHAR(100)  NOT NULL,
  email       VARCHAR(255),
  created_at  TIMESTAMP     NOT NULL,
  status      SMALLINT      DEFAULT 1
)
SERVER remote_prod
OPTIONS (
  schema_name 'public',     -- 遠端 schema 名稱
  table_name  'users'       -- 遠端表格名稱
);

方法 B:IMPORT FOREIGN SCHEMA 批次匯入

-- 建立本地 schema 接收匯入的表格
CREATE SCHEMA IF NOT EXISTS remote_schema;

-- 一次匯入遠端整個 schema 下的所有表格
IMPORT FOREIGN SCHEMA public
  FROM SERVER remote_prod
  INTO remote_schema;

-- 只匯入指定表格
IMPORT FOREIGN SCHEMA public
  LIMIT TO (users, orders, products)
  FROM SERVER remote_prod
  INTO remote_schema;

-- 排除特定表格
IMPORT FOREIGN SCHEMA public
  EXCEPT (audit_logs, temp_tables)
  FROM SERVER remote_prod
  INTO remote_schema;

-- 確認匯入結果
SELECT foreign_table_schema, foreign_table_name
FROM information_schema.foreign_tables
ORDER BY 1, 2;

IMPORT FOREIGN SCHEMA 是生產環境的首選方法——自動對應欄位類型,省去手動維護的成本。

file_fdw:讀取 CSV 與日誌檔

file_fdw 是官方提供的 FDW,用於將本地檔案系統上的 CSV 或分隔符號格式文字檔映射為 Foreign Table:

-- 安裝 file_fdw 擴展
CREATE EXTENSION IF NOT EXISTS file_fdw;

-- 建立 Server(每個 file_fdw 通常只需一個)
CREATE SERVER local_files
  FOREIGN DATA WRAPPER file_fdw;

-- 建立 Foreign Table 對應 CSV 檔案
CREATE FOREIGN TABLE import_orders (
  order_id    INTEGER,
  customer    TEXT,
  amount      NUMERIC(10, 2),
  order_date  DATE,
  status      TEXT
)
SERVER local_files
OPTIONS (
  filename '/var/lib/postgresql/imports/orders.csv',
  format   'csv',
  header   'true',       -- 第一列為標頭
  delimiter ',',         -- 欄位分隔符號
  null     'NULL',       -- NULL 值的表示方式
  encoding 'UTF8'
);

-- 查詢 CSV 資料(如同本地表格)
SELECT * FROM import_orders WHERE amount > 1000;

讀取 PostgreSQL CSV 日誌

-- 將 PostgreSQL CSV 日誌映射為可查詢的表格
CREATE FOREIGN TABLE pg_log_import (
  log_time              TIMESTAMP WITH TIME ZONE,
  user_name             TEXT,
  database_name         TEXT,
  process_id            INTEGER,
  connection_from       TEXT,
  session_id            TEXT,
  session_line_num      BIGINT,
  command_tag           TEXT,
  session_start_time    TIMESTAMP WITH TIME ZONE,
  virtual_transaction_id TEXT,
  transaction_id        BIGINT,
  error_severity        TEXT,
  sql_state_code        TEXT,
  message               TEXT,
  detail                TEXT,
  hint                  TEXT,
  internal_query        TEXT,
  internal_query_pos    INTEGER,
  context               TEXT,
  query                 TEXT,
  query_pos             INTEGER,
  location              TEXT,
  application_name      TEXT
)
SERVER local_files
OPTIONS (
  filename '/var/log/postgresql/postgresql-2026-07-13.csv',
  format   'csv'
);

-- 分析日誌中的錯誤
SELECT log_time, error_severity, sql_state_code, message
FROM pg_log_import
WHERE error_severity IN ('ERROR', 'FATAL', 'PANIC')
ORDER BY log_time DESC
LIMIT 20;

注意:file_fdw 只支援讀取操作(唯讀),且檔案必須對 PostgreSQL 進程可讀。

跨資料庫 JOIN 實戰

以下示範實際業務場景:本地資料庫存放訂單,遠端資料庫存放客戶資料:

-- JOIN 本地訂單與遠端客戶資料
SELECT
  o.order_id,
  c.name        AS customer_name,
  c.email,
  o.total,
  o.status,
  o.created_at
FROM orders o
JOIN remote_schema.customers c ON c.id = o.customer_id
WHERE o.status = 'pending'
  AND o.created_at >= CURRENT_DATE - INTERVAL '7 days'
ORDER BY o.total DESC
LIMIT 100;

-- 彙總查詢:各國客戶的總消費金額
SELECT
  c.country,
  COUNT(o.order_id)   AS order_count,
  SUM(o.total)        AS total_revenue
FROM orders o
JOIN remote_schema.customers c ON c.id = o.customer_id
WHERE c.status = 'active'
GROUP BY c.country
ORDER BY total_revenue DESC;

用 EXPLAIN 驗證 Pushdown

使用 EXPLAIN (VERBOSE, ANALYZE) 可以查看實際發送至遠端的 SQL 語句:

-- 查看查詢計畫,確認 Pushdown 是否生效
EXPLAIN (VERBOSE, ANALYZE)
SELECT c.name, SUM(o.total)
FROM orders o
JOIN remote_schema.customers c ON c.id = o.customer_id
WHERE c.country = 'TW'
  AND o.created_at >= '2026-01-01'
GROUP BY c.name;

典型輸出(Pushdown 生效時):

HashAggregate  (cost=... rows=... width=...)
  ->  Hash Join  (cost=... rows=... width=...)
        Hash Cond: (o.customer_id = c.id)
        ->  Seq Scan on orders o  (cost=...)
              Filter: (created_at >= '2026-01-01')
        ->  Hash  (cost=... rows=... width=...)
              ->  Foreign Scan on customers c  (cost=...)
                    Output: c.name, c.id
                    Remote SQL: SELECT id, name FROM public.customers
                                WHERE ((country = 'TW'))

Remote SQL 欄位顯示實際傳送至遠端的 SQL——確認 WHERE 條件已被成功下推。

FDW 結合 Table Partitioning:分散式架構

PostgreSQL 的宣告式分割可以與 FDW 結合,建立橫跨多台實例的分散式查詢架構:

-- 建立分割主表(本地)
CREATE TABLE sales (
  sale_id    BIGINT   NOT NULL,
  region     TEXT     NOT NULL,
  amount     NUMERIC,
  sale_date  DATE     NOT NULL
) PARTITION BY LIST (region);

-- 建立各遠端伺服器
CREATE SERVER shard_apac FOREIGN DATA WRAPPER postgres_fdw
  OPTIONS (host '10.0.1.10', port '5432', dbname 'sales_db');
CREATE SERVER shard_emea FOREIGN DATA WRAPPER postgres_fdw
  OPTIONS (host '10.0.1.20', port '5432', dbname 'sales_db');
CREATE SERVER shard_amer FOREIGN DATA WRAPPER postgres_fdw
  OPTIONS (host '10.0.1.30', port '5432', dbname 'sales_db');

-- 建立 User Mapping(略:同前述步驟三)

-- 建立 Foreign Table 作為分割分片
CREATE FOREIGN TABLE sales_apac
  PARTITION OF sales FOR VALUES IN ('TW', 'JP', 'SG', 'AU', 'KR')
  SERVER shard_apac
  OPTIONS (schema_name 'public', table_name 'sales');

CREATE FOREIGN TABLE sales_emea
  PARTITION OF sales FOR VALUES IN ('DE', 'FR', 'GB', 'NL', 'IT')
  SERVER shard_emea
  OPTIONS (schema_name 'public', table_name 'sales');

CREATE FOREIGN TABLE sales_amer
  PARTITION OF sales FOR VALUES IN ('US', 'CA', 'MX', 'BR')
  SERVER shard_amer
  OPTIONS (schema_name 'public', table_name 'sales');

查詢時,PostgreSQL 自動根據分割鍵路由至對應分片:

-- Partition Pruning + FDW Pushdown 雙重優化
-- 只查詢 shard_apac,不會存取其他分片
SELECT region, SUM(amount) AS total
FROM sales
WHERE region = 'TW'
  AND sale_date >= '2026-01-01'
GROUP BY region;

-- 跨分片彙總:自動查詢所有分片並合併結果
SELECT region, SUM(amount)
FROM sales
WHERE sale_date BETWEEN '2026-01-01' AND '2026-03-31'
GROUP BY region
ORDER BY 2 DESC;

這種架構實現了類似 Citus 的水平擴展,而 Partition Pruning 確保查詢只存取相關分片。

效能調校

fetch_size 調整

-- fetch_size 控制每次批次取回的列數(預設 100,對大型結果集過低)

-- Server 層級設定
ALTER SERVER remote_prod OPTIONS (SET fetch_size '5000');

-- Foreign Table 層級設定(覆蓋 Server 設定)
ALTER FOREIGN TABLE remote_schema.large_table
  OPTIONS (ADD fetch_size '10000');

-- batch_size:INSERT 批次大小(PostgreSQL 14 新增)
ALTER SERVER remote_prod OPTIONS (ADD batch_size '100');

非同步執行(PostgreSQL 14+)

-- async_capable 允許非同步並行掃描多個 Foreign Table
ALTER SERVER remote_prod OPTIONS (ADD async_capable 'true');
ALTER SERVER remote_backup OPTIONS (ADD async_capable 'true');

-- 設定並行工作者數量
SET max_parallel_workers_per_gather = 4;

use_remote_estimate

-- 讓規劃器使用遠端 EXPLAIN 取得更準確的成本估算
-- 代價:每次規劃都會對遠端發出 EXPLAIN 查詢
ALTER SERVER remote_prod OPTIONS (ADD use_remote_estimate 'true');

-- 或手動調整統計樣本大小
ALTER FOREIGN TABLE remote_schema.customers
  ALTER COLUMN id SET STATISTICS 1000;
ANALYZE remote_schema.customers;

連線管理

-- keep_connections 減少重複建立連線的成本(預設開啟)
ALTER SERVER remote_prod OPTIONS (SET keep_connections 'on');

-- 查看目前的 FDW 連線狀態
SELECT * FROM postgres_fdw_get_connections();

-- 手動清除閒置的 FDW 連線
SELECT postgres_fdw_disconnect('remote_prod');
SELECT postgres_fdw_disconnect_all();

常見陷阱與注意事項

陷阱說明建議
交易一致性FDW 不完全支援分散式 2PC盡量只做讀取,寫入改用本地 queue
統計資訊不準Foreign Table 預設統計不準確啟用 use_remote_estimate 或定期 ANALYZE
Pushdown 失效使用本地函數或型別轉換會阻止 PushdownEXPLAIN 確認 Remote SQL
fetch_size 過低預設 100,大量資料查詢效能差調高至 1000-10000
密碼明文存放User Mapping 密碼存在 pg_user_mappings限制 pg_user_mappings 存取權限
SAVEPOINT 相容性ROLLBACK TO SAVEPOINT 會關閉遠端游標避免在 FDW 查詢中使用 SAVEPOINT
時區不一致本地與遠端時區設定不同統一使用 UTC
網路延遲高延遲網路會拖慢所有查詢優先使用 Pushdown,減少往返次數

其他常用 FDW

oracle_fdw

-- 存取 Oracle 資料庫
CREATE EXTENSION oracle_fdw;

CREATE SERVER oracle_db
  FOREIGN DATA WRAPPER oracle_fdw
  OPTIONS (dbserver '//10.0.1.50:1521/ORCL');

CREATE USER MAPPING FOR app_user
  SERVER oracle_db
  OPTIONS (user 'oracle_user', password 'oracle_pass');

CREATE FOREIGN TABLE oracle_customers (
  cust_id   NUMERIC(10),
  cust_name VARCHAR(100),
  region    VARCHAR(50)
)
SERVER oracle_db
OPTIONS (schema 'HR', table 'CUSTOMERS');

mysql_fdw

-- 存取 MySQL / MariaDB
CREATE EXTENSION mysql_fdw;

CREATE SERVER mysql_db
  FOREIGN DATA WRAPPER mysql_fdw
  OPTIONS (host '10.0.1.60', port '3306');

CREATE USER MAPPING FOR postgres
  SERVER mysql_db
  OPTIONS (username 'mysql_user', password 'mysql_pass');

CREATE FOREIGN TABLE mysql_products (
  id    INT         NOT NULL,
  name  VARCHAR(255),
  price NUMERIC(10, 2),
  stock INT
)
SERVER mysql_db
OPTIONS (dbname 'shop', table_name 'products');

FDW Pushdown 支援矩陣

FDWWHEREJOINSortAggregateLIMIT寫入
postgres_fdw完整完整(14+)完整(14+)完整(11+)完整(14+)支援
file_fdw本地過濾不支援不支援不支援不支援不支援
oracle_fdw完整部分部分部分支援支援
mysql_fdw完整部分部分部分支援支援
tds_fdw部分不支援不支援不支援支援支援

即使 FDW 宣告支援某類 Pushdown,規劃器仍可能基於成本估算選擇不使用——務必透過 EXPLAIN 確認。

FDW 版本演進

PostgreSQL 版本FDW 重要功能
9.1基本 FDW API,可讀取外部資料
9.3可寫入 Foreign Table(INSERT/UPDATE/DELETE)
9.6Upper-Relation Pushdown API
10宣告式分割 + FDW 可作為分片
11postgres_fdw Aggregate Pushdown
12Partition-wise JOIN/Aggregate Pushdown
14async_capable 非同步掃描、Sort/LIMIT Pushdown、batch_size
15RLS 與 FDW 整合改善
16postgres_fdw 效能改善,更精準 Cost 估算
17Foreign Table 統計資訊收集效能優化

總結

Foreign Data Wrapper 是 PostgreSQL 作為資料整合中心的核心機制。掌握以下重點:

  1. 三層架構:Foreign Data Wrapper → Foreign Server → User Mapping → Foreign Table
  2. Pushdown 是效能關鍵:用 EXPLAIN (VERBOSE) 確認 Remote SQL 確實包含過濾條件
  3. IMPORT FOREIGN SCHEMA 比手動建立 Foreign Table 更省事、更不容易出錯
  4. fetch_size 預設 100 太低,生產環境建議調至 1000-10000
  5. FDW + Partitioning 可實現類似 Citus 的分散式查詢架構
  6. 交易一致性限制:FDW 不支援完整的 2PC,跨庫寫入需謹慎

下一篇,我們將探討 PostgreSQL 規則系統(Rule System)——了解 PostgreSQL 獨有的查詢重寫機制,以及它與 Trigger 的差異和適用場景。

BenZ Software Developer

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