Skip to content

postgres_fdw 外部数据包装器 ​

上一节我们把 FDW 的五层对象模型讲清了。从本节开始,逐个展开具体实现。首先是其中最常用、功能最完整的一个:postgres_fdw——它让你能把另一台 PostgreSQL 实例(或同一实例的另一个数据库)当成本地表来读写。

为什么它最值得先学?三个原因:它是 PostgreSQL 官方 contrib 模块、随主版本一起维护;它支持完整的下推(WHERE / JOIN / 聚集 / ORDER BY / LIMIT)和读写,能力远超大多数第三方 FDW;它的选项体系也是后面理解"FDW 调优"的基准范本。

本节会把 postgres_fdw 的全部连接层与表层选项逐项讲清,包括默认值和常见误用。

前置知识

阅读本节前,建议先了解:外部数据包装器概览

基础概念 ​

postgres_fdw 是什么 ​

postgres_fdw 是一个外部数据包装器,专门用于连接另一个 PostgreSQL 服务端。它通过 PostgreSQL 的线协议(wire protocol)与远端通信——注意,它走的是客户端/服务端协议,而不是直接读写远端的数据文件。这一点很重要,意味着:

  • 远端必须正常运行且允许你通过 TCP/Unix socket 连接;
  • 远端上的权限、RLS、触发器依然生效;
  • 你无法绕过远端做"物理级"读取。

从功能覆盖度看,它支持:

能力支持情况
SELECT支持
INSERT / UPDATE / DELETE支持(可通过 updatable 'false' 关闭)
COPY 到/从外部表支持
TRUNCATE支持
WHERE 条件下推支持
JOIN 下推(两端在同一远端)支持
聚集下推(GROUP BY / sum / count 等)支持
ORDER BY / LIMIT / OFFSET 下推支持(有排序规则限制)
DISTINCT 下推支持
排序(Sort)下推支持(有排序规则限制)
INSERT ... ON CONFLICT DO NOTHING支持
INSERT ... ON CONFLICT DO UPDATE不支持
并行扫描下推有限支持(需 async_capable)
跨库两阶段提交(2PC)支持(需配置 max_prepared_transactions)

这张表的实操含义是:绝大多数"读取 + 联合分析"类需求,postgres_fdw 都能高效完成。

连接是如何建立的 ​

postgres_fdw 使用 连接缓存:当本地会话第一次访问某个外部服务器时建立连接,之后同一会话内的后续查询会复用该连接,不会每次都重新握手。

text
本地会话 A ──┐
             ├──► 连接缓存(按 server + user mapping 分组)
本地会话 B ──┘         │
                       ├─ 到 remote_pg / 映射 app_user
                       └─ 到 remote_pg / 映射 report_user(不同凭据 → 不同连接)

注意:连接按"服务器 + 用户映射"组合缓存。如果你给同一个 server 建了两份不同用户的 user mapping,会各占一条连接。这在连接数紧张时需要留意。

读写方向与"下推"的整体图景 ​

一条查询在 postgres_fdw 里的处理过程可以分为"能下推的"和"不能下推的"两类:

text
                本地(Local)                        远端(Remote)
   ┌────────────────────────────────┐     ┌──────────────────────────────┐
   │ 解析 → 优化                     │     │                              │
   │   ├─ 可下推部分 ──────────────────────►│ 执行 Remote SQL              │
   │   │   WHERE / JOIN / GROUP BY    │     │  └─ 返回结果行               │
   │   │   ORDER BY / LIMIT / 聚集     │     │                              │
   │   └─ 不可下推部分                 │◄──── │                              │
   │       本地再加工(本地函数、       │     │                              │
   │       跨库 JOIN、本地排序规则)    │     │                              │
   └────────────────────────────────┘     └──────────────────────────────┘

判断一条查询快不快,基本就看"下推了多少"。用 EXPLAIN (VERBOSE) 看 Remote SQL 那一行,是每个 DBA 的日常动作。

选项分层的四种位置 ​

postgres_fdw 的选项不能随意摆放,不同选项只能出现在特定的对象层级上:

层级对象典型选项
连接层SERVERhost、port、dbname、updatable、use_remote_estimate、fdw_startup_cost、fdw_tuple_cost、fetch_size、batch_size、extensions、async_capable、parallel_commit、parallel_abort、analyze_sampling
凭据层USER MAPPINGuser、password、password_required、sslcert、sslkey(后两者须远端编译支持 SSL)
表层FOREIGN TABLEschema_name、table_name、use_remote_estimate、fetch_size、batch_size、updatable、analyze_sampling
列层FOREIGN TABLE 的列column_name

注意:extensions 选项只能设置在 server 级,不能设在表级。这是新手最容易踩的坑之一。

同时,password_required 只有超级用户才能设置。

表级选项会覆盖同名的 server 级选项。而列级选项 column_name 用于把本地列名映射到远端不同的列名。

语法与用法 ​

完整创建流程 ​

sql
-- 1. 安装扩展
CREATE EXTENSION postgres_fdw;

-- 2. 创建服务器,把远端连接信息集中在这里
CREATE SERVER remote_pg
    FOREIGN DATA WRAPPER postgres_fdw
    OPTIONS (
        host      '10.0.0.5',
        port      '5432',
        dbname    'production',
        updatable 'true'
    );

-- 3. 创建用户映射,指定远端登录凭据
CREATE USER MAPPING FOR CURRENT_USER
    SERVER remote_pg
    OPTIONS (user 'fdw_reader', password 'strong_password');

-- 4. 导入远端 schema 下的表定义
IMPORT FOREIGN SCHEMA public
    LIMIT TO (orders, order_items, customers)
    FROM SERVER remote_pg
    INTO public;

上面四步是最小可用流程。如果想完全手工控制列定义,也可以省略第 4 步,直接 CREATE FOREIGN TABLE。

手工创建外部表 ​

sql
CREATE FOREIGN TABLE remote_orders (
    id          bigint,
    tenant_id   bigint,
    amount      numeric(12,2),
    status      text,
    created_at  timestamptz
)
SERVER remote_pg
OPTIONS (
    schema_name 'public',
    table_name  'orders',
    fetch_size  '1000',
    updatable   'false'
);

当远端表名与本地外部表名不同,或需要给单张表单独设定 fetch_size、关闭写入时,就用这种手工方式。

修改选项 ​

sql
-- 修改 server 级选项
ALTER SERVER remote_pg OPTIONS (SET fetch_size '2000', ADD async_capable 'true');

-- 修改表级选项
ALTER FOREIGN TABLE remote_orders OPTIONS (SET fetch_size '5000');

-- 删除一个选项(恢复为继承上层或默认值)
ALTER FOREIGN TABLE remote_orders OPTIONS (DROP fetch_size);

-- 修改用户映射的凭据
ALTER USER MAPPING FOR CURRENT_USER SERVER remote_pg
    OPTIONS (SET password 'new_password');

-- 修改列名映射
ALTER FOREIGN TABLE remote_orders ALTER COLUMN amount
    OPTIONS (ADD column_name 'order_amount');

OPTIONS 子句里的三个动作词:ADD(新增,已存在会报错)、SET(设置/覆盖)、DROP(移除)。用错动作词会直接报错,这是很常见的低级失误。

批量导入与排除 ​

sql
-- 只导入指定的几张表
IMPORT FOREIGN SCHEMA public
    LIMIT TO (orders, order_items)
    FROM SERVER remote_pg INTO public;

-- 导入除黑名单外的所有表
IMPORT FOREIGN SCHEMA public
    EXCEPT (internal_tmp, audit_log, schema_migrations)
    FROM SERVER remote_pg INTO reporting;

IMPORT FOREIGN SCHEMA 需要 server 的 USAGE 权限,以及目标 schema 的 CREATE 权限。注意导入是一次性快照:远端后来新增的列不会自动同步,需要重新导入或用 ALTER FOREIGN TABLE ... ADD COLUMN。

详细说明 ​

连接选项:host / port / dbname ​

这三个是最基础的选项,含义与 libpq 的连接参数一致:

sql
CREATE SERVER remote_pg
    FOREIGN DATA WRAPPER postgres_fdw
    OPTIONS (host '10.0.0.5', port '5432', dbname 'production');

如果不指定 host,postgres_fdw 会使用远端上的本地 Unix socket——这只有在你与远端在同一台机器时才有效。跨机器必须显式写 host。

需要注意的权限问题:非超级用户使用 FDW 时,host 等连接参数受到 pg_hba.conf 远端配置的约束;此外这里的 host 是由本地配置决定的,不需要(也无法)在本地 postgresql.conf 中预先声明。

写入控制:updatable ​

updatable 控制外部表是否允许 INSERT/UPDATE/DELETE:

sql
-- 在 server 级全局关闭写入
ALTER SERVER remote_pg OPTIONS (SET updatable 'false');

-- 单独为某张表开启(表级覆盖 server 级)
ALTER FOREIGN TABLE remote_orders OPTIONS (SET updatable 'true');

默认值为 true。生产环境中,面向报表用途的 server 建议强制设为 false,从源头杜绝误写("从源头"指在定义层面就限死,而不是靠应用层的自觉)。

估算方式:use_remote_estimate ​

这是影响执行计划质量的关键选项。

sql
-- 让远端返回真实的估算代价
ALTER SERVER remote_pg OPTIONS (SET use_remote_estimate 'true');

默认值 false。两者的区别:

取值行为代价
false(默认)用本地保存的外部表统计信息估算快,但统计可能过时
true向远端发送 EXPLAIN 请求,用远端真实估算准确,但每次生成计划都要多一次网络往返

推荐策略:对涉及多表 JOIN、计划容易选错的关键查询,在表级开启 use_remote_estimate;对简单查询保持默认关闭,避免计划生成开销。

sql
-- 只给大表开启,避免全局开销
ALTER FOREIGN TABLE remote_orders OPTIONS (SET use_remote_estimate 'true');

代价权重:fdw_startup_cost 与 fdw_tuple_cost ​

这两个选项用于修正优化器对"外部表扫描"的代价估计。

sql
ALTER SERVER remote_pg
    OPTIONS (SET fdw_startup_cost '100', SET fdw_tuple_cost '0.2');
  • fdw_startup_cost:外部扫描的"启动成本",默认 100。它把"建立连接、网络握手"的开销折算成代价。
  • fdw_tuple_cost:每行的额外传输成本,默认 0.2。用于反映"每一行都要过网络"的代价。

这两个值是"虚"的代价单位,不是毫秒。调大的效果是让优化器更不愿意选择外部表扫描(比如更倾向于先做本地过滤、或换个 JOIN 顺序)。

注意:fdw_tuple_cost 的默认值是 0.2,不是某些资料里写的 0.01。误用偏小的值会让优化器低估跨网络传输的代价,从而选出"看起来便宜、实际很慢"的计划。

抓取批大小:fetch_size ​

fetch_size 控制每次从远端取回多少行:

sql
ALTER SERVER remote_pg OPTIONS (SET fetch_size '1000');

默认值 100。它的作用类似 libpq 游标的批量大小。

  • 值偏小:往返次数多,网络延迟敏感场景(高延迟链路)下会明显变慢;
  • 值偏大:单次响应内存占用上升,且在带 LIMIT 的查询里可能多取了不需要的行。

经验:内网低延迟可以设 1000~5000;跨地域高延迟链路可设更大,但要配合 LIMIT 使用。

批量写入大小:batch_size ​

batch_size 控制 INSERT 时每批提交多少行(PG14 引入):

sql
ALTER FOREIGN TABLE remote_orders OPTIONS (SET batch_size '100');

默认值 1,即默认逐行发送。批量写入时调大它可以显著减少往返次数:

sql
-- 批量导入时临时调大
ALTER FOREIGN TABLE remote_orders OPTIONS (SET batch_size '500');

INSERT INTO remote_orders
SELECT * FROM local_staging;

-- 用完记得恢复
ALTER FOREIGN TABLE remote_orders OPTIONS (SET batch_size '1');

注意:batch_size 只影响写入路径,对 SELECT 无效。SELECT 的批量由 fetch_size 控制。两者别搞混。

异步与并行:async_capable / parallel_commit / parallel_abort ​

sql
ALTER SERVER remote_pg
    OPTIONS (ADD async_capable 'true',
             ADD parallel_commit 'true',
             ADD parallel_abort 'true');
  • async_capable:默认 false。开启后,对同一远端的多个外部表扫描可以异步并行执行,减少串行等待。
  • parallel_commit:默认 false。控制本地提交事务时,是否并行提交所有远端事务。
  • parallel_abort:默认 false。控制本地事务回滚时,是否并行回滚所有远端事务。

这三个选项在"一次事务里写多个远端"时价值最大。示例:本地事务往 3 个远端的表各写一批数据,开启 parallel_commit 后,三次远端提交可以并发进行,而不是依次等待。

它们的代价是占用更多远端连接,因此当远端连接数受限时要谨慎开启。

扩展下推:extensions ​

extensions 让 postgres_fdw 了解远端安装了哪些扩展,从而知道哪些函数可以安全下推:

sql
-- 远端装了 postgis,告诉 FDW 这些函数可以下推
ALTER SERVER remote_pg OPTIONS (ADD extensions 'postgis');

-- 多个扩展用逗号分隔
ALTER SERVER remote_pg OPTIONS (SET extensions 'postgis,hstore');

这条选项解决的是一个真实痛点:默认情况下,postgres_fdw 只知道 PostgreSQL 内置函数,遇到扩展提供的函数(如 PostGIS 的地理函数)会认为"本地不认识",于是不下推,改为把整张表拉回本地逐行计算——性能会急剧下降。

注意:extensions 只能设置在 server 级。原因很直观:扩展是服务器级别的属性,同一台远端的扩展集合是一样的。

统计采样:analyze_sampling ​

控制 ANALYZE 外部表时采用哪种采样方式:

sql
ALTER FOREIGN TABLE remote_orders OPTIONS (SET analyze_sampling 'random');

可选值与默认值:

取值含义
off不做采样,直接用远端的统计结果
random随机采样
system按系统页采样(类似 SYSTEM 采样)
bernoulli按行伯努利采样
auto(默认)由实现自行选择

当远端表非常大、或远端数据分布特殊(例如按时间顺序物理聚集)时,选择不同的采样方式会影响统计准确度,进而影响计划质量。

用户映射的凭据选项 ​

sql
CREATE USER MAPPING FOR app_user
    SERVER remote_pg
    OPTIONS (
        user              'fdw_reader',
        password          'strong_password',
        password_required 'true',
        sslcert           '/etc/ssl/client.crt',
        sslkey            '/etc/ssl/client.key'
    );
选项说明
user远端登录用户名
password远端登录密码
password_required是否必须提供密码;默认 true。设为 false 允许无密码连接。只有超级用户能设置
sslcert客户端证书路径(需远端支持 SSL)
sslkey客户端私钥路径(需远端支持 SSL)

sslkey 建议设为仅属主可读(chmod 600),否则部分 SSL 实现会拒绝使用。

列名映射:column_name ​

当远端列名与本地不同时,用列级选项对齐:

sql
CREATE FOREIGN TABLE remote_orders (
    id      bigint,
    amount  numeric(12,2) OPTIONS (column_name 'order_amount')
)
SERVER remote_pg
OPTIONS (schema_name 'public', table_name 'orders');

没有 column_name 时,匹配依据是列名本身——这一点与"按位置匹配"是相反的,务必注意。如果远端表列顺序变了但列名没变,本地映射依然正确;反之,若远端列改名,本地就会取到 NULL。

两阶段提交与 max_prepared_transactions ​

当你在一个本地事务里同时修改多个远端时,为保证原子性,postgres_fdw 会使用两阶段提交(Two-Phase Commit,2PC):

sql
BEGIN;
INSERT INTO remote_orders_a (...) VALUES (...);   -- 远端 A
INSERT INTO remote_orders_b (...) VALUES (...);   -- 远端 B
COMMIT;   -- 内部:PREPARE TRANSACTION 于 A、B → COMMIT PREPARED

要让它工作,远端必须设置 max_prepared_transactions 大于 0:

ini
# 远端 postgresql.conf
max_prepared_transactions = 100

注意:max_prepared_transactions 默认为 0,即禁用两阶段提交。如果保持默认值,跨两个远端的写事务会失败,报错类似 prepared transactions are disabled。这是跨库写入最常见的配置坑。

顺带一提,这也是 parallel_commit / parallel_abort 生效的前提。

支持与不支持的写入语法 ​

sql
-- 支持:冲突时忽略
INSERT INTO remote_orders (id, amount) VALUES (1, 100)
    ON CONFLICT DO NOTHING;

-- 不支持:ON CONFLICT DO UPDATE
INSERT INTO remote_orders (id, amount) VALUES (1, 100)
    ON CONFLICT (id) DO UPDATE SET amount = EXCLUDED.amount;  -- 错误!

ON CONFLICT DO UPDATE 需要远端返回更新后的行,而 FDW 接口目前无法可靠地完成这一往返,因此不被支持。若业务必须"存在则更新",可改为先 UPDATE,再对未命中的行 INSERT(或用 MERGE 场景在远端本地执行)。

另外,ON CONFLICT DO NOTHING 时不建议依赖"唯一索引推断"——因为本地无法完整获知远端的索引定义(省略推断说明会让计划更稳妥)。

不支持 ONLY 与继承 ​

sql
-- 错误!对远端表而言 ONLY 没有实际效果
SELECT * FROM ONLY remote_orders;

外部表不存在继承关系,因此 ONLY 无法起到"只查父表"的作用。写法上 PostgreSQL 可能接受,但语义上与不带 ONLY 完全相同。

排序规则一致性的要求 ​

想让 ORDER BY 下推到远端,本地的排序规则(collation)必须与远端一致。若不一致,FDW 只能把数据拉回本地再排:

sql
-- 若本地是 en_US.UTF-8 而远端是 C,此处 ORDER BY 可能无法下推
SELECT * FROM remote_orders ORDER BY status;

同理,列类型也建议两端完全一致。类型不同会引入隐式转换,可能阻断下推,或导致比较语义差异。

实战示例 ​

示例一:跨库关联分析,验证下推效果 ​

场景:本地有 tenants(租户维表),远端有 orders(订单事实表)。要统计每个租户近 30 天的订单总额。

sql
CREATE EXTENSION IF NOT EXISTS postgres_fdw;

CREATE SERVER remote_pg FOREIGN DATA WRAPPER postgres_fdw
    OPTIONS (host '10.0.0.5', port '5432', dbname 'production');

CREATE USER MAPPING FOR CURRENT_USER SERVER remote_pg
    OPTIONS (user 'fdw_reader', password 'strong_password');

IMPORT FOREIGN SCHEMA public LIMIT TO (orders)
    FROM SERVER remote_pg INTO public;

-- 远端统计信息先收集一次
ANALYZE remote_orders;

先看"远端聚合 + 本地 JOIN"的写法:

sql
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t.name,
       sum(r.amount) AS total
FROM tenants t
JOIN (
    SELECT tenant_id, sum(amount) AS amount
    FROM remote_orders
    WHERE created_at >= now() - interval '30 days'
    GROUP BY tenant_id
) r ON r.tenant_id = t.id
GROUP BY t.name
ORDER BY total DESC;
text
                             QUERY PLAN
--------------------------------------------------------------------
 Sort
   Output: t.name, (sum(r.amount))
   Sort Key: (sum(r.amount)) DESC
   ->  HashAggregate
         Output: t.name, sum(r.amount)
         Group Key: t.name
         ->  Hash Join
               Output: t.name, r.amount
               Hash Cond: (r.tenant_id = t.id)
               ->  Subquery Scan on r
                     Output: r.tenant_id, r.amount
                     ->  Foreign Scan
                           Output: remote_orders.tenant_id, (sum(remote_orders.amount))
                           Relations: Aggregate on (public.remote_orders)
                           Remote SQL: SELECT tenant_id, sum(amount) FROM public.orders
                                       WHERE (created_at >= (now() - '30 days'::interval))
                                       GROUP BY tenant_id
               ->  Hash
                     Output: t.id, t.name
                     ->  Seq Scan on public.tenants t
(16 rows)

这里可以看到:几十万条订单在远端就被聚合成了若干租户行,网络只传输聚合结果,本地只做最后的小规模 JOIN 与排序。这就是 FDW 最理想的使用形态。

示例二:跨库写入与两阶段提交 ​

假设远端已启用 max_prepared_transactions = 100,我们要把一个本地批次同时写入两个远端。

sql
-- 远端 A
CREATE SERVER remote_pg_a FOREIGN DATA WRAPPER postgres_fdw
    OPTIONS (host '10.0.0.5', port '5432', dbname 'biz_a');
-- 远端 B
CREATE SERVER remote_pg_b FOREIGN DATA WRAPPER postgres_fdw
    OPTIONS (host '10.0.0.6', port '5432', dbname 'biz_b');

CREATE USER MAPPING FOR CURRENT_USER SERVER remote_pg_a
    OPTIONS (user 'writer', password 'pwd_a');
CREATE USER MAPPING FOR CURRENT_USER SERVER remote_pg_b
    OPTIONS (user 'writer', password 'pwd_b');

CREATE FOREIGN TABLE remote_ledger_a (
    id bigint, amount numeric(12,2), created_at timestamptz
) SERVER remote_pg_a OPTIONS (schema_name 'public', table_name 'ledger');

CREATE FOREIGN TABLE remote_ledger_b (
    id bigint, amount numeric(12,2), created_at timestamptz
) SERVER remote_pg_b OPTIONS (schema_name 'public', table_name 'ledger');

-- 开启批量与并行提交,减少往返
ALTER FOREIGN TABLE remote_ledger_a OPTIONS (ADD batch_size '200');
ALTER FOREIGN TABLE remote_ledger_b OPTIONS (ADD batch_size '200');
ALTER SERVER remote_pg_a OPTIONS (ADD parallel_commit 'true');
ALTER SERVER remote_pg_b OPTIONS (ADD parallel_commit 'true');

执行一笔原子性写入:

sql
BEGIN;
INSERT INTO remote_ledger_a (id, amount, created_at)
SELECT id, amount, now() FROM local_ledger_batch;

INSERT INTO remote_ledger_b (id, amount, created_at)
SELECT id, -amount, now() FROM local_ledger_batch;   -- 借贷两边
COMMIT;
text
BEGIN
INSERT 0 1000
INSERT 0 1000
COMMIT

若远端 max_prepared_transactions 仍为默认的 0,同样的 COMMIT 会失败:

text
ERROR:  prepared transactions are disabled
HINT:  Set max_prepared_transactions to a nonzero value on the remote server.

示例三:用扩展函数下推检索地理数据 ​

远端装了 PostGIS,我们要按距离过滤:

sql
-- 提前告知 FDW 远端有 postgis,让 st_dwithin 能够下推
ALTER SERVER remote_geo OPTIONS (ADD extensions 'postgis');

EXPLAIN (VERBOSE, COSTS OFF)
SELECT id, name
FROM remote_shops
WHERE ST_DWithin(location, ST_MakePoint(116.4, 39.9)::geography, 5000);

-- 输出中可见 ST_DWithin 出现在 Remote SQL 里,说明已下推

若没有 extensions 声明,同样的查询会变成"把 remote_shops 全表拉回本地,再逐行计算 ST_DWithin",Remote SQL 里不会出现该函数。

注意事项 ​

fdw_tuple_cost 默认值是 0.2,不是 0.01

这是最常见的口口相传错误。写文档或调参时务必以官方值为准:

sql
-- 默认值参考
-- fdw_startup_cost = 100
-- fdw_tuple_cost   = 0.2
ALTER SERVER remote_pg OPTIONS (SET fdw_tuple_cost '0.2');

max_prepared_transactions 默认为 0,跨库写会失败

ini
# 错误!默认值下无法进行跨远端的两阶段提交
max_prepared_transactions = 0

必须显式配置为非 0 并重启远端。

ON CONFLICT DO UPDATE 不被支持

sql
-- 错误!FDW 无法完成该往返
INSERT INTO remote_orders (id, amount) VALUES (1, 100)
    ON CONFLICT (id) DO UPDATE SET amount = EXCLUDED.amount;

extensions 选项不能设在表级

sql
-- 错误!extensions 只能是 server 级选项
ALTER FOREIGN TABLE remote_orders OPTIONS (ADD extensions 'postgis');

fetch_size 与 batch_size 用途不同,别互换

sql
-- 错误用法的思考方式:想加快写入却去调 fetch_size
ALTER FOREIGN TABLE t OPTIONS (SET fetch_size '5000');  -- 这对 INSERT 无帮助

读用 fetch_size,写用 batch_size。

没有 ANY 的隐式类型转换会阻断下推

sql
-- 不推荐:类型不一致可能导致无法下推
SELECT * FROM remote_orders WHERE amount > '100';  -- amount 是 numeric,字面量是 unknown 时还好,
                                                    -- 但显式类型不同的比较需谨慎
SELECT * FROM remote_orders WHERE amount > 100::text;  -- 错误!类型不匹配,会引入本地转换

IMPORT FOREIGN SCHEMA 是一次性快照

远端加列后本地不会自动同步:

sql
-- 远端新增了 refund_amount 列,本地仍是旧定义
\d remote_orders   -- 看不到 refund_amount
-- 需要手工补列
ALTER FOREIGN TABLE remote_orders ADD COLUMN refund_amount numeric(12,2);

不要用 CASCADE 删除 server

sql
DROP SERVER remote_pg CASCADE;  -- 不推荐:静默删掉该 server 下所有外部表

远端 RLS 会照常生效

FDW 走的是正常客户端协议,远端若启用了行级安全,你在本地只会看到远端策略允许的行。若出现"数据莫名缺失",先排查远端 RLS 与映射用户权限。

别把 use_remote_estimate 全局打开

sql
-- 不推荐:每次生成计划都多一次网络往返
ALTER SERVER remote_pg OPTIONS (SET use_remote_estimate 'true');

对大表、复杂 JOIN 局部开启即可。

密码明文存储

USER MAPPING 的 password 存在 pg_user_mapping 中。请限制超级用户范围、避免把含密码的 DDL 写进版本库,更推荐 SSL 证书或 .pgpass 方案。

最佳实践 ​

用专用只读账号,并在 server 级关闭写入

sql
CREATE SERVER report_pg FOREIGN DATA WRAPPER postgres_fdw
    OPTIONS (host '10.0.0.5', port '5432', dbname 'report', updatable 'false');

创建后立刻 ANALYZE 关键外部表

sql
ANALYZE remote_orders;
ANALYZE remote_order_items;

用表级 use_remote_estimate 精准提升关键查询

sql
ALTER FOREIGN TABLE remote_orders OPTIONS (ADD use_remote_estimate 'true');

大批量写入时临时调大 batch_size,写完恢复

sql
ALTER FOREIGN TABLE remote_orders OPTIONS (SET batch_size '500');
-- ...执行批量导入...
ALTER FOREIGN TABLE remote_orders OPTIONS (SET batch_size '1');

为跨库写入配置远端 max_prepared_transactions

ini
max_prepared_transactions = 100

对多远端并发写入开启并行提交

sql
ALTER SERVER remote_pg_a OPTIONS (ADD parallel_commit 'true', ADD parallel_abort 'true');

高延迟链路上调大 fetch_size

sql
ALTER SERVER remote_pg OPTIONS (SET fetch_size '2000');

远端有扩展时必须声明 extensions

sql
ALTER SERVER remote_geo OPTIONS (SET extensions 'postgis,pg_trgm');

保证两端列类型与排序规则一致

sql
-- 远端:status text COLLATE "en_US.utf8"
-- 本地也声明同样的 collation,ORDER BY 才能下推
CREATE FOREIGN TABLE remote_orders (
    status text COLLATE "en_US.utf8"
) SERVER remote_pg OPTIONS (schema_name 'public', table_name 'orders');

用 EXPLAIN (VERBOSE) 检查 Remote SQL 养成习惯

sql
EXPLAIN (VERBOSE, COSTS OFF)
SELECT tenant_id, sum(amount) FROM remote_orders WHERE status = 'paid' GROUP BY tenant_id;

为外部表统一放在独立 schema

sql
CREATE SCHEMA IF NOT EXISTS fdw_data;
IMPORT FOREIGN SCHEMA public EXCEPT (schema_migrations)
    FROM SERVER remote_pg INTO fdw_data;

限制报表角色的资源消耗

sql
ALTER ROLE report_user SET statement_timeout = '60s';
ALTER ROLE report_user SET idle_in_transaction_session_timeout = '30s';

给外部表加注释,标注来源与负责人

sql
COMMENT ON FOREIGN TABLE remote_orders IS
    '来源:report_pg(10.0.0.5) public.orders;离线报表专用';

定期探测远端可达性

sql
SELECT count(*) FROM remote_orders WHERE id = -1;   -- 极轻量探活

远端表结构变更后重新导入或补列

sql
-- 变更后重新导入该表定义
DROP FOREIGN TABLE remote_orders;
IMPORT FOREIGN SCHEMA public LIMIT TO (orders) FROM SERVER remote_pg INTO public;
ANALYZE remote_orders;

下一节

继续学习:file_fdw 文件外部数据包装器

参考链接 ​