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 使用 连接缓存:当本地会话第一次访问某个外部服务器时建立连接,之后同一会话内的后续查询会复用该连接,不会每次都重新握手。
本地会话 A ──┐
├──► 连接缓存(按 server + user mapping 分组)
本地会话 B ──┘ │
├─ 到 remote_pg / 映射 app_user
└─ 到 remote_pg / 映射 report_user(不同凭据 → 不同连接)注意:连接按"服务器 + 用户映射"组合缓存。如果你给同一个 server 建了两份不同用户的 user mapping,会各占一条连接。这在连接数紧张时需要留意。
读写方向与"下推"的整体图景
一条查询在 postgres_fdw 里的处理过程可以分为"能下推的"和"不能下推的"两类:
本地(Local) 远端(Remote)
┌────────────────────────────────┐ ┌──────────────────────────────┐
│ 解析 → 优化 │ │ │
│ ├─ 可下推部分 ──────────────────────►│ 执行 Remote SQL │
│ │ WHERE / JOIN / GROUP BY │ │ └─ 返回结果行 │
│ │ ORDER BY / LIMIT / 聚集 │ │ │
│ └─ 不可下推部分 │◄──── │ │
│ 本地再加工(本地函数、 │ │ │
│ 跨库 JOIN、本地排序规则) │ │ │
└────────────────────────────────┘ └──────────────────────────────┘判断一条查询快不快,基本就看"下推了多少"。用 EXPLAIN (VERBOSE) 看 Remote SQL 那一行,是每个 DBA 的日常动作。
选项分层的四种位置
postgres_fdw 的选项不能随意摆放,不同选项只能出现在特定的对象层级上:
| 层级 | 对象 | 典型选项 |
|---|---|---|
| 连接层 | SERVER | host、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 MAPPING | user、password、password_required、sslcert、sslkey(后两者须远端编译支持 SSL) |
| 表层 | FOREIGN TABLE | schema_name、table_name、use_remote_estimate、fetch_size、batch_size、updatable、analyze_sampling |
| 列层 | FOREIGN TABLE 的列 | column_name |
注意:
extensions选项只能设置在 server 级,不能设在表级。这是新手最容易踩的坑之一。同时,
password_required只有超级用户才能设置。
表级选项会覆盖同名的 server 级选项。而列级选项 column_name 用于把本地列名映射到远端不同的列名。
语法与用法
完整创建流程
-- 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。
手工创建外部表
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、关闭写入时,就用这种手工方式。
修改选项
-- 修改 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(移除)。用错动作词会直接报错,这是很常见的低级失误。
批量导入与排除
-- 只导入指定的几张表
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 的连接参数一致:
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:
-- 在 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
这是影响执行计划质量的关键选项。
-- 让远端返回真实的估算代价
ALTER SERVER remote_pg OPTIONS (SET use_remote_estimate 'true');默认值 false。两者的区别:
| 取值 | 行为 | 代价 |
|---|---|---|
false(默认) | 用本地保存的外部表统计信息估算 | 快,但统计可能过时 |
true | 向远端发送 EXPLAIN 请求,用远端真实估算 | 准确,但每次生成计划都要多一次网络往返 |
推荐策略:对涉及多表 JOIN、计划容易选错的关键查询,在表级开启 use_remote_estimate;对简单查询保持默认关闭,避免计划生成开销。
-- 只给大表开启,避免全局开销
ALTER FOREIGN TABLE remote_orders OPTIONS (SET use_remote_estimate 'true');代价权重:fdw_startup_cost 与 fdw_tuple_cost
这两个选项用于修正优化器对"外部表扫描"的代价估计。
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 控制每次从远端取回多少行:
ALTER SERVER remote_pg OPTIONS (SET fetch_size '1000');默认值 100。它的作用类似 libpq 游标的批量大小。
- 值偏小:往返次数多,网络延迟敏感场景(高延迟链路)下会明显变慢;
- 值偏大:单次响应内存占用上升,且在带
LIMIT的查询里可能多取了不需要的行。
经验:内网低延迟可以设 1000~5000;跨地域高延迟链路可设更大,但要配合 LIMIT 使用。
批量写入大小:batch_size
batch_size 控制 INSERT 时每批提交多少行(PG14 引入):
ALTER FOREIGN TABLE remote_orders OPTIONS (SET batch_size '100');默认值 1,即默认逐行发送。批量写入时调大它可以显著减少往返次数:
-- 批量导入时临时调大
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
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 了解远端安装了哪些扩展,从而知道哪些函数可以安全下推:
-- 远端装了 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 外部表时采用哪种采样方式:
ALTER FOREIGN TABLE remote_orders OPTIONS (SET analyze_sampling 'random');可选值与默认值:
| 取值 | 含义 |
|---|---|
off | 不做采样,直接用远端的统计结果 |
random | 随机采样 |
system | 按系统页采样(类似 SYSTEM 采样) |
bernoulli | 按行伯努利采样 |
auto(默认) | 由实现自行选择 |
当远端表非常大、或远端数据分布特殊(例如按时间顺序物理聚集)时,选择不同的采样方式会影响统计准确度,进而影响计划质量。
用户映射的凭据选项
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
当远端列名与本地不同时,用列级选项对齐:
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):
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:
# 远端 postgresql.conf
max_prepared_transactions = 100注意:
max_prepared_transactions默认为0,即禁用两阶段提交。如果保持默认值,跨两个远端的写事务会失败,报错类似prepared transactions are disabled。这是跨库写入最常见的配置坑。
顺带一提,这也是 parallel_commit / parallel_abort 生效的前提。
支持与不支持的写入语法
-- 支持:冲突时忽略
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 与继承
-- 错误!对远端表而言 ONLY 没有实际效果
SELECT * FROM ONLY remote_orders;外部表不存在继承关系,因此 ONLY 无法起到"只查父表"的作用。写法上 PostgreSQL 可能接受,但语义上与不带 ONLY 完全相同。
排序规则一致性的要求
想让 ORDER BY 下推到远端,本地的排序规则(collation)必须与远端一致。若不一致,FDW 只能把数据拉回本地再排:
-- 若本地是 en_US.UTF-8 而远端是 C,此处 ORDER BY 可能无法下推
SELECT * FROM remote_orders ORDER BY status;同理,列类型也建议两端完全一致。类型不同会引入隐式转换,可能阻断下推,或导致比较语义差异。
实战示例
示例一:跨库关联分析,验证下推效果
场景:本地有 tenants(租户维表),远端有 orders(订单事实表)。要统计每个租户近 30 天的订单总额。
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"的写法:
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; 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,我们要把一个本地批次同时写入两个远端。
-- 远端 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');执行一笔原子性写入:
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;BEGIN
INSERT 0 1000
INSERT 0 1000
COMMIT若远端 max_prepared_transactions 仍为默认的 0,同样的 COMMIT 会失败:
ERROR: prepared transactions are disabled
HINT: Set max_prepared_transactions to a nonzero value on the remote server.示例三:用扩展函数下推检索地理数据
远端装了 PostGIS,我们要按距离过滤:
-- 提前告知 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
这是最常见的口口相传错误。写文档或调参时务必以官方值为准:
-- 默认值参考
-- fdw_startup_cost = 100
-- fdw_tuple_cost = 0.2
ALTER SERVER remote_pg OPTIONS (SET fdw_tuple_cost '0.2');max_prepared_transactions 默认为 0,跨库写会失败
# 错误!默认值下无法进行跨远端的两阶段提交
max_prepared_transactions = 0必须显式配置为非 0 并重启远端。
ON CONFLICT DO UPDATE 不被支持
-- 错误!FDW 无法完成该往返
INSERT INTO remote_orders (id, amount) VALUES (1, 100)
ON CONFLICT (id) DO UPDATE SET amount = EXCLUDED.amount;extensions 选项不能设在表级
-- 错误!extensions 只能是 server 级选项
ALTER FOREIGN TABLE remote_orders OPTIONS (ADD extensions 'postgis');fetch_size 与 batch_size 用途不同,别互换
-- 错误用法的思考方式:想加快写入却去调 fetch_size
ALTER FOREIGN TABLE t OPTIONS (SET fetch_size '5000'); -- 这对 INSERT 无帮助读用 fetch_size,写用 batch_size。
没有 ANY 的隐式类型转换会阻断下推
-- 不推荐:类型不一致可能导致无法下推
SELECT * FROM remote_orders WHERE amount > '100'; -- amount 是 numeric,字面量是 unknown 时还好,
-- 但显式类型不同的比较需谨慎
SELECT * FROM remote_orders WHERE amount > 100::text; -- 错误!类型不匹配,会引入本地转换IMPORT FOREIGN SCHEMA 是一次性快照
远端加列后本地不会自动同步:
-- 远端新增了 refund_amount 列,本地仍是旧定义
\d remote_orders -- 看不到 refund_amount
-- 需要手工补列
ALTER FOREIGN TABLE remote_orders ADD COLUMN refund_amount numeric(12,2);不要用 CASCADE 删除 server
DROP SERVER remote_pg CASCADE; -- 不推荐:静默删掉该 server 下所有外部表远端 RLS 会照常生效
FDW 走的是正常客户端协议,远端若启用了行级安全,你在本地只会看到远端策略允许的行。若出现"数据莫名缺失",先排查远端 RLS 与映射用户权限。
别把 use_remote_estimate 全局打开
-- 不推荐:每次生成计划都多一次网络往返
ALTER SERVER remote_pg OPTIONS (SET use_remote_estimate 'true');对大表、复杂 JOIN 局部开启即可。
密码明文存储
USER MAPPING 的 password 存在 pg_user_mapping 中。请限制超级用户范围、避免把含密码的 DDL 写进版本库,更推荐 SSL 证书或 .pgpass 方案。
最佳实践
用专用只读账号,并在 server 级关闭写入
CREATE SERVER report_pg FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host '10.0.0.5', port '5432', dbname 'report', updatable 'false');创建后立刻 ANALYZE 关键外部表
ANALYZE remote_orders;
ANALYZE remote_order_items;用表级 use_remote_estimate 精准提升关键查询
ALTER FOREIGN TABLE remote_orders OPTIONS (ADD use_remote_estimate 'true');大批量写入时临时调大 batch_size,写完恢复
ALTER FOREIGN TABLE remote_orders OPTIONS (SET batch_size '500');
-- ...执行批量导入...
ALTER FOREIGN TABLE remote_orders OPTIONS (SET batch_size '1');为跨库写入配置远端 max_prepared_transactions
max_prepared_transactions = 100对多远端并发写入开启并行提交
ALTER SERVER remote_pg_a OPTIONS (ADD parallel_commit 'true', ADD parallel_abort 'true');高延迟链路上调大 fetch_size
ALTER SERVER remote_pg OPTIONS (SET fetch_size '2000');远端有扩展时必须声明 extensions
ALTER SERVER remote_geo OPTIONS (SET extensions 'postgis,pg_trgm');保证两端列类型与排序规则一致
-- 远端: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 养成习惯
EXPLAIN (VERBOSE, COSTS OFF)
SELECT tenant_id, sum(amount) FROM remote_orders WHERE status = 'paid' GROUP BY tenant_id;为外部表统一放在独立 schema
CREATE SCHEMA IF NOT EXISTS fdw_data;
IMPORT FOREIGN SCHEMA public EXCEPT (schema_migrations)
FROM SERVER remote_pg INTO fdw_data;限制报表角色的资源消耗
ALTER ROLE report_user SET statement_timeout = '60s';
ALTER ROLE report_user SET idle_in_transaction_session_timeout = '30s';给外部表加注释,标注来源与负责人
COMMENT ON FOREIGN TABLE remote_orders IS
'来源:report_pg(10.0.0.5) public.orders;离线报表专用';定期探测远端可达性
SELECT count(*) FROM remote_orders WHERE id = -1; -- 极轻量探活远端表结构变更后重新导入或补列
-- 变更后重新导入该表定义
DROP FOREIGN TABLE remote_orders;
IMPORT FOREIGN SCHEMA public LIMIT TO (orders) FROM SERVER remote_pg INTO public;
ANALYZE remote_orders;下一节
继续学习:file_fdw 文件外部数据包装器