文章目录
-
- [1. 背景](#1. 背景)
- [2. postgres_fdw 是什么](#2. postgres_fdw 是什么)
-
- [2.1 核心能力](#2.1 核心能力)
- [2.2 优点](#2.2 优点)
- [2.3 缺点](#2.3 缺点)
- [3. 实现步骤](#3. 实现步骤)
-
- [3.1 在主库执行 DDL(已沉淀为 Flyway 迁移脚本)](#3.1 在主库执行 DDL(已沉淀为 Flyway 迁移脚本))
- [3.2 修改用户映射(如远程账号变更)](#3.2 修改用户映射(如远程账号变更))
- [3.3 清理(卸载 FDW 时使用)](#3.3 清理(卸载 FDW 时使用))
- [4. 关键设计点](#4. 关键设计点)
-
- [4.1 不需要 `@DS` 注解切换数据源](#4.1 不需要
@DS注解切换数据源) - [4.2 IMPORT FOREIGN SCHEMA 必须用 EXECUTE 包裹](#4.2 IMPORT FOREIGN SCHEMA 必须用 EXECUTE 包裹)
- [4.3 幂等性](#4.3 幂等性)
- [4.1 不需要 `@DS` 注解切换数据源](#4.1 不需要
- [5. 与其他跨库方案对比](#5. 与其他跨库方案对比)
- [6. 注意事项与生产建议](#6. 注意事项与生产建议)
1. 背景
在动态数据源场景下,主库(advanced-java-infra-common,存放 file_detail)与从库(advanced-java-infra-auth,存放 sys_user)位于不同的物理 PostgreSQL 实例。要展示"文件列表 + 创建人姓名",常见三种实现:
| 方案 | 实现 | 缺点 |
|---|---|---|
| 内存循环 JOIN | 主库查文件列表 → 循环切换从库逐条查 sys_user.name |
N+1 查询、跨数据源事务复杂、网络开销大 |
| 应用层批量 IN 查询 | 主库查文件 → 收集 userIds → 从库一次 IN 查询 → 内存拼接 |
仍跨两次连接,无法走 SQL 引擎执行计划 |
| postgres_fdw 跨库 JOIN | 一条 SQL,由 PostgreSQL 引擎透明跨库 JOIN | 依赖 PG 扩展,需 DBA 协助配置 |
本文档介绍第三种方案。
2. postgres_fdw 是什么
postgres_fdw (Foreign Data Wrapper,外部数据包装器)是 PostgreSQL 官方自带的核心扩展,实现了 SQL/MED 标准。
2.1 核心能力
- 跨库访问:在一个 PG 数据库中,像访问本地表一样直接读写另一个远程 PG 数据库的表
- 逻辑投影而非物理复制:本地不复制远程数据,执行查询时动态把 SQL 翻译并下发到远端,再把结果拉回本地
- 支持读写 :不仅支持
SELECT,也支持INSERT / UPDATE / DELETE远程表
2.2 优点
- 完全透明 :对上层 Java/Spring 应用零侵入,业务代码只写普通 SQL(
SELECT/JOIN/UPDATE),完全感知不到数据在另一个库 - 算子下推(Push-Down 优化) :
WHERE/ORDER BY/LIMIT等谓词会被推到远程库执行,避免把全表拉回本地,极大节省网络带宽 - 支持事务:与 PG 本地事务无缝衔接(仍需关注分布式事务边界)
2.3 缺点
- 网络依赖高:每次查询都是实时跨网络交互,两端延迟高时性能断崖式下跌
- 锁与事务风险:跨库写操作耗时过长,可能同时锁住本地和远程资源,引入分布式事务复杂度
- 复杂 JOIN 限制:本地大表与远程大表 JOIN 时,PG 可能需要把其中一张整张拉过来,存在撑爆内存/临时盘的风险
- 权限要求 :
CREATE EXTENSION需要SUPERUSER或具有创建扩展权限的角色
3. 实现步骤
3.1 在主库执行 DDL(已沉淀为 Flyway 迁移脚本)
迁移脚本:V20250603_2__create_fdw_auth.sql,按以下 5 步配置 FDW,所有 DDL 均幂等:
sql
-- 1. 安装扩展
CREATE EXTENSION IF NOT EXISTS postgres_fdw;
-- 2. 创建外部服务器(远程库连接信息)
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_foreign_server WHERE srvname = 'auth_server') THEN
CREATE SERVER auth_server
FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (
host '127.0.0.1',
port '5432',
dbname 'advanced-java-infra-auth'
);
END IF;
END $$;
-- 3. 创建用户映射(本地用户 → 远程用户)
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_user_mappings
WHERE srvname = 'auth_server' AND usename = CURRENT_USER
) THEN
CREATE USER MAPPING FOR CURRENT_USER
SERVER auth_server
OPTIONS (user 'xx', password 'xx');
END IF;
END $$;
-- 4. 创建专门的外部表 schema(隔离命名空间)
CREATE SCHEMA IF NOT EXISTS fdw_auth;
-- 5. 从远程 server 导入 sys_user 表到 fdw_auth schema
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.foreign_tables
WHERE foreign_table_schema = 'fdw_auth' AND foreign_table_name = 'sys_user'
) THEN
EXECUTE 'IMPORT FOREIGN SCHEMA public LIMIT TO (sys_user)
FROM SERVER auth_server INTO fdw_auth';
END IF;
END $$;
3.2 修改用户映射(如远程账号变更)
sql
ALTER USER MAPPING FOR CURRENT_USER
SERVER auth_server
OPTIONS (
SET user 'xx',
SET password 'xx'
);
3.3 清理(卸载 FDW 时使用)
sql
-- 1. 删除外部表 schema 及其所有外部表定义
DROP SCHEMA IF EXISTS fdw_auth CASCADE;
-- 2. 删除用户映射
DROP USER MAPPING IF EXISTS FOR CURRENT_USER SERVER auth_server;
-- 3. 删除外部服务器
DROP SERVER IF EXISTS auth_server CASCADE;
-- 4. (可选)删除扩展
-- DROP EXTENSION IF EXISTS postgres_fdw;
4. 关键设计点
4.1 不需要 @DS 注解切换数据源
FDW 外部表对 MyBatis 完全透明:
- Mapper 中 SQL 只发往主库
- 跨库 JOIN 由 PostgreSQL 引擎在主库内部透明完成
- 因此
test4()方法不需要@DS("xxx")注解,也不需要@DSTransactional
4.2 IMPORT FOREIGN SCHEMA 必须用 EXECUTE 包裹
IMPORT FOREIGN SCHEMA ... INTO <schema> 是 PostgreSQL 的实用工具命令 (utility statement),不能在 PL/pgSQL DO 块中直接出现 ------PL/pgSQL 解析器会把 INTO fdw_auth 误识别为变量赋值,报错:
ERROR: "fdw_auth" is not a known variable
正确做法是用 EXECUTE '...' 动态执行:
sql
EXECUTE 'IMPORT FOREIGN SCHEMA public LIMIT TO (sys_user)
FROM SERVER auth_server INTO fdw_auth';
4.3 幂等性
所有 DDL 都使用 IF NOT EXISTS 或包裹在 DO $$ ... $$ + IF NOT EXISTS (SELECT 1 FROM pg_xxx) 中,保证脚本可重复执行不报错,符合 Flyway 迁移最佳实践。
5. 与其他跨库方案对比
| 维度 | 内存循环 | postgres_fdw | ShardingSphere Federation |
|---|---|---|---|
| 代码侵入 | 高(需手写循环 + 切数据源) | 零(普通 JOIN SQL) | 低(配置驱动) |
| 性能 | 差(N+1) | 良(依赖网络 + 算子下推) | 中 |
| 算子下推 | 无 | 有 | 部分支持 |
| 跨数据库引擎 | 支持(任意) | 仅 PostgreSQL | 多种 |
| 事务一致性 | 复杂(需 XA / Seata) | 本地事务即可读 | 需配合 XA |
| 运维复杂度 | 低 | 中(需配置外部表 + 远程账号) | 较高 |
6. 注意事项与生产建议
- 远程账号最小权限 :用户映射中的远程账号只授予
SELECT(或必要的写权限),避免SUPERUSER - 密码安全 :迁移脚本明文密码仅限开发环境;生产环境建议
- 使用 Flyway
placeholder注入:OPTIONS (user '${remote.user}', password '${remote.password}') - 或改为
.pgpass/ Kubernetes Secret 注入
- 使用 Flyway
- 网络与超时 :必要时在
CREATE SERVER OPTIONS中加connect_timeout、fetch_size等参数 - 统计信息 :远程表的统计信息默认不会自动同步,复杂 JOIN 前可执行
ANALYZE fdw_auth.sys_user提升执行计划质量 - 避免大表 × 大表 JOIN:本地大表 JOIN 远程大表可能引发整表拉取;如果数据量大,考虑离线同步而非实时 FDW
- 监控:跨库查询的延迟、错误率应纳入慢 SQL 监控,及时发现 FDW 链路异常