基于 postgres_fdw 的跨库查询方案

文章目录

    • [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 幂等性)
    • [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 核心能力

  1. 跨库访问:在一个 PG 数据库中,像访问本地表一样直接读写另一个远程 PG 数据库的表
  2. 逻辑投影而非物理复制:本地不复制远程数据,执行查询时动态把 SQL 翻译并下发到远端,再把结果拉回本地
  3. 支持读写 :不仅支持 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. 注意事项与生产建议

  1. 远程账号最小权限 :用户映射中的远程账号只授予 SELECT(或必要的写权限),避免 SUPERUSER
  2. 密码安全 :迁移脚本明文密码仅限开发环境;生产环境建议
    • 使用 Flyway placeholder 注入:OPTIONS (user '${remote.user}', password '${remote.password}')
    • 或改为 .pgpass / Kubernetes Secret 注入
  3. 网络与超时 :必要时在 CREATE SERVER OPTIONS 中加 connect_timeoutfetch_size 等参数
  4. 统计信息 :远程表的统计信息默认不会自动同步,复杂 JOIN 前可执行 ANALYZE fdw_auth.sys_user 提升执行计划质量
  5. 避免大表 × 大表 JOIN:本地大表 JOIN 远程大表可能引发整表拉取;如果数据量大,考虑离线同步而非实时 FDW
  6. 监控:跨库查询的延迟、错误率应纳入慢 SQL 监控,及时发现 FDW 链路异常

相关推荐
敲个大西瓜1 小时前
Java并发实用干货
java
1368木林森1 小时前
【Spring源码17·完结篇】SpringBoot核心注解+高频坑点+失效场景万字全集!收官Spring全家桶源码系列
java·spring boot·后端
南山十一少1 小时前
基于 Quartz 组件在 Spring Boot 框架下的周期任务调度实验
java·spring boot·spring
Leo.yuan1 小时前
MySQL到Hive数据同步怎么选工具?FineDataLink全链路方案实测
数据库·hive·mysql
Database_Cool_1 小时前
数据仓库物化视图是什么?阿里云 AnalyticDB MySQL 实时物化视图最佳实践
数据库·数据仓库·mysql
罗超驿1 小时前
14.LeetCode 438 题解:滑动窗口+哈希表找所有字母异位词
java·算法·leetcode
码不停蹄的玄黓1 小时前
Java线程池生命周期
java·开发语言
学习要积极1 小时前
Spring AI Alibaba-ChatClient
java·人工智能·spring
武子康1 小时前
Java-15 深入浅出MyBatis 分页与通用 Mapper 实战:PageHelper + tk.mybatis 从配置到分页查询
java·后端