引言
PostgreSQL 中的外部数据封装器(Foreign Data Wrapper, FDW)是一种扩展,允许您像访问 PostgreSQL 数据库中的表一样,访问和操作存储在外部数据源中的数据。FDW 使 PostgreSQL 能够与多种数据存储系统(包括关系型和非关系型)进行集成,并以统一的方式在 PostgreSQL 环境中呈现这些数据。将一个表拆分成多个位于远程的较小表的操作也称为 sharding
。外部数据库节点有时被称为外部分片或简略为分片。在本文中,我们将探讨在使用 postgres_fdw
处理外部 SELECT 查询时,其内部的具体工作流程。
以下是一些背景历史:
2003 年,SQL 标准中新增了一个名为 SQL/MED(SQL 管理外部数据)的规范,该标准定义了远程访问数据库的 SQL 规范。 2011 年,PostgreSQL 9.1 版本发布,开始支持此标准的只读操作。 2013 年,新增了对写入操作的支持。 当前有许多不同的 FDW 可供使用,允许 PostgreSQL 连接到各种远程数据存储(包括其他关系型数据库管理系统到平面文件等)。 这些 FDW 大多数没有 PostgreSQL 全球开发组(PGDG)的官方支持,一些项目仍处于 beta 阶段。请谨慎使用!
一些与 FDW 相关的文档:
https://wiki.postgresql.org/wiki/Foreign_data_wrappers https://pgxn.org/tag/fdw/ https://www.postgresql.org/docs/15/fdwhandler.html
配置一个简单的 FDW 环境
在配置之前,请确保已将 postgresql_fdw
扩展编译并安装到您的 PostgreSQL 二进制安装路径中。我们将配置一个由 1 个协调节点和 4 个外部数据节点组成的分片环境。
初始化所有数据库实例$ initdb -D c1
$ initdb -D d1
$ initdb -D d2
$ initdb -D d3
$ initdb -D d4
$ initdb -D d1
$ initdb -D d2
$ initdb -D d3
$ initdb -D d4
编辑 d1 到 d4 节点的配置文件,并将它们的端口号分别更改为 5433、5434、5435、5436,同时保持 c1 节点运行在默认端口 5432。这样做的原因是我们将在同一台机器上运行所有节点以便演示。
在 c1 节点:postgres=# CREATE EXTENSION postgres_fdw;
postgres=# CREATE SERVER s1 FOREIGN DATA WRAPPER postgres_fdw OPTIONS (dbname 'postgres', host '127.0.0.1', port '5433');
postgres=# CREATE SERVER s2 FOREIGN DATA WRAPPER postgres_fdw OPTIONS (dbname 'postgres', host '127.0.0.1', port '5434');
postgres=# CREATE SERVER s3 FOREIGN DATA WRAPPER postgres_fdw OPTIONS (dbname 'postgres', host '127.0.0.1', port '5435');
postgres=# CREATE SERVER s4 FOREIGN DATA WRAPPER postgres_fdw OPTIONS (dbname 'postgres', host '127.0.0.1', port '5436');
postgres=# CREATE USER MAPPING for postgres SERVER s1 OPTIONS(user 'postgres');
postgres=# CREATE USER MAPPING for postgres SERVER s2 OPTIONS(user 'postgres');
postgres=# CREATE USER MAPPING for postgres SERVER s3 OPTIONS(user 'postgres');
postgres=# CREATE USER MAPPING for postgres SERVER s4 OPTIONS(user 'postgres');
postgres=# CREATE TABLE t (a INT, b TEXT) PARTITION BY RANGE;
postgres=# CREATE TABLE t_local PARTITION OF t FOR VALUES FROM (1) TO (1000);
postgres=# CREATE FOREIGN TABLE t_s1 PARTITION OF t FOR VALUES FROM (1001) TO (2000) SERVER s1 OPTIONS(schema_name 'public', table_name 't');
postgres=# CREATE FOREIGN TABLE t_s2 PARTITION OF t FOR VALUES FROM (2001) TO (3000) SERVER s1 OPTIONS(schema_name 'public', table_name 't');
postgres=# CREATE FOREIGN TABLE t_s3 PARTITION OF t FOR VALUES FROM (3001) TO (4000) SERVER s1 OPTIONS(schema_name 'public', table_name 't');
postgres=# CREATE FOREIGN TABLE t_s4 PARTITION OF t FOR VALUES FROM (4001) TO (5000) SERVER s1 OPTIONS(schema_name 'public', table_name 't');
在所有数据节点:postgres=# CREATE TABLE t (a INT, b TEXT);
到此为止,我们完成了一个简单的 postgres_fdw
设置。当数据插入到 c1 节点的 t 表时,数据可以存储在 c1 节点本地(范围 1 到 1000),也可以根据范围值分发到外部节点(d1 ~ d4)。
从外部节点查询数据
postgres_fdw
并不会简单地转发您的 SELECT 查询并将其发送到外部节点处理,等待响应。这种方式的性能极低。与常规的 SELECT 查询一样,外部查询也需要仔细的分析和规划,以确保性能。远程查询通常包括以下几个步骤:
分析器 – 创建查询树 根据 pg_catalog.pg_class
和pg_catalog.pg_foreign_table
中的外部表定义创建查询树。连接到外部服务器 规划器从 pg_catalog.pg_foreign_server
和pg_catalog.pg_user_mapping
获取网络和用户映射信息。通过 FDW 连接到远程服务器。 postgres_fdw
使用 libpq 连接到远程 PostgreSQL 数据库。规划 规划器使用 EXPLAIN 命令创建计划树。 postgres_fdw
支持使用 EXPLAIN 获取远程表的统计信息,但也可以使用本地默认值。EXPLAIN 返回远程服务器的启动成本和总成本,允许规划器准确评估语句。 use_remote_estimate
参数可以通过 ALTER SERVER 修改(默认关闭)。反解析 规划器生成的计划树通过 FDW 获取了远程服务器的扫描路径。 postgres_fdw
负责解析计划树并重构一组适合远程服务器的 SQL 语句。扩展查询协议 + 游标 postgres_fdw
使用扩展查询协议,并借助游标从远程节点检索或获取数据。它使用可重复读的隔离级别,以确保在事务期间数据的一致性,不会被其他并发事务更改。
相关的 FDW API
在访问远程服务器执行查询之前,规划器会调用 IsForeignScanParallelSafe()
、GetForeignPlan()
和 GetForeignPaths()
来了解远程数据的规模,并确定是否将 WHERE 条件评估本地执行或外部执行等。
准备好之后,使用 BeginForeignScan 和 IterateForeignScan 来获取一行或多行数据。 最后,通过 EndForeignScan 释放资源,包括释放已分配的内存、打开的文件和连接到外部数据源的连接等。
IsForeignScanParallelSafe() 是否支持并行扫描。如果支持,当执行大量数据的 SELECT 时,可以启动多个工作进程并行执行。 GetForeignRelSize() 获取远程表的大小估算。规划器在扫描开始前调用。 GetForeignPaths() 获取所有可能的远程表扫描执行路径,并将每个扫描路径添加到扫描路径列表中。 GetForeignPlan() 在查询规划的最后阶段,从选定的访问路径创建 ForeignScan 计划节点。 此函数非常重要,它包含要输出的目标列表、执行的 WHERE 语句以及由谁执行。 BeginForeignScan() 准备执行远程扫描。负责扫描开始前的必要初始化工作。 IterateForeignScan() 从远程节点获取一行数据,并将其返回为元组槽,结束时返回 NULL。 EndForeignScan() 结束扫描并释放资源。释放通过 palloc 分配的内存,清理打开的文件和连接到远程服务器的连接。
总结
本文对 postgres_fdw
内部工作原理的快速概述,涵盖了 SELECT 查询的基本知识。
- 推荐阅读 -
直播回顾|从 Oracle 到 PostgreSQL:IvorySQL v4 如何让数据库迁移更丝滑
深入 PostgreSQL 内部:5 个关键阶段拆解查询处理全流程
突破关系型边界:PostgreSQL 的 JSON 如何重新定义数据敏捷性
手把手教你在 openKylin 上部署 IvorySQL 4.4
在 PostgreSQL 中设置调试环境以更好地理解 OpenSSL API
深入理解 PostgreSQL Planner:简化扫描路径与查询计划
DeepSeek 加持!IvorySQL 文档智能助手正式上线!
如何利用 PostgreSQL 的 JSONB API 作为扩展的轻量级 JSON 解析器
IvorySQL v4 逻辑复制槽同步功能解析:高可用场景下的数据连续性保障
「2024 年度技术精华盘点」IvorySQL & PostgreSQL 技术干货全解析!
IvorySQL 4.0 之 Invisible Column 功能解析
IvorySQL 4.0 之兼容 Oracle 包功能设计思路解读
IvorySQL 升级指南:从 3.x 到 4.0 的平滑过渡
IvorySQL 4.0 发布:全面支持 PostgreSQL 17





