基于 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 链路异常

相关推荐
Zane19948 小时前
用了Optional,为什么NPE还是防不住?
java·后端
子非鱼a8 小时前
【SQL注入】 ShadowCart
sql
大模型码小白8 小时前
告别造假数据,直接连数据库查真实时序数据喂给 TimechoAI 大模型
java·数据库·人工智能·microsoft·架构
企业数字化笔记8 小时前
固定资产Excel批量导入怎么防重复?资产编码、重复检查和错误回执
java·后端·excel
晚安日记wanna8 小时前
大表 DDL 面试翻车现场Online DDL 为什么还会锁死业务
数据库·面试·架构
IvorySQL8 小时前
PostgreSQL 日报|建库策略致备库静默丢数据(9 月 12 日)
数据库·人工智能·postgresql
这个DBA有点耶8 小时前
当Agent从“写SQL”变成“执行SQL”:数据库安全模型需要重新设计
数据库·aigc·dba
晚安日记wanna8 小时前
MySQL 主从延迟别只答并行复制
数据库·面试·架构
golang学习记8 小时前
IDEA 2026.3 EAP新特性:自动识别Spring Boot的新 profile 了
java·spring boot·intellij-idea
SamDeepThinking8 小时前
Java 17内存屏障:为什么需要,怎么用
java·后端·面试