深入解析 PostgreSQL 外部数据封装器(FDW)的 SELECT 查询执行机制
引言
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
编辑 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 查询的基本知识。 > 本文由博客一文多发平台 OpenWrite 发布!

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
在Oracle到GreatSQL迁移中排序规则改变引发的乱码问题分析及解决
一、引言 某老系统数据库从 Oracle 迁移至 GreatSQL 过程中,首批迁移(存储过程、表结构、基础数据)顺利完成。然而,第二批数据迁移时出现主键冲突问题:原Oracle数据库中存在主键字段A与a(忽略大小写后视为相同值),但 GreatSQL 默认排序规则 utf8mb4_0900_ai_ci 不区分大小写,导致主键冲突。 为解决此问题,将排序规则调整为 utf8mb4_0900_bin 以区分大小写。但调整后,Java程序读取中文字段时出现乱码(如“好”显示为“好”),直接影响业务功能。本文从环境兼容性、驱动版本、字符编解码机制等角度深入分析问题根源,并提供三种解决方案。 二、环境说明与问题背景 关键组件版本: 组件 版本号 备注 数据库 GreatSQL 8.0.32-26 默认字符集utf8mb4 jdk 1.7.0_80 旧版本,升级成本高 驱动版本 mysql-connector-java 5.1.46 官方已停止维护 字符集 utf8mb4 未变动 排序规则 utf8mb4_0900_ai_ci->utf8mb4_0900_bin 变更后引发乱码 核心矛...
- 下一篇
MCP 协议为何不如你想象的安全?从技术专家视角解读
编者按: 模型上下文协议(MCP)究竟安全可靠吗?当你通过 MCP 插件让 AI Agent 访问公司文档、员工聊天记录或客户信息时,你真的了解潜在的安全风险吗? 文章详细剖析了 MCP 存在的四大问题:协议自身的安全性不足,包括缺乏标准化的身份认证机制及存在可能执行恶意代码的风险;用户体验方面的局限,如缺乏工具风险分级和成本控制;大语言模型安全方面的挑战,特别是提示词注入和敏感数据泄露的风险;以及 LLM 本身的技术局限,导致在比较复杂的工具组合下性能可能下降而非提升。作者通过具体案例和技术分析,揭示了当前 MCP 协议中的各种漏洞与缺陷。 作者 | Shrivu Shankar 编译 | 岳扬 就在过去短短几周内,模型上下文协议(Model Context Protocol,MCP)[1]已迅速成为事实意义上的第三方数据源和工具与 LLM 驱动的聊天对话及智能体整合的标准。虽然互联网上充斥着各种可以通过该协议实现的炫酷应用场景,但同时也存在着很多漏洞和限制。 作为 MCP 爱好者,我将在本文列举其中的一些问题,并就该协议未来的发展标准、开发者及用户需要注意的重要事项进行阐述。其中有...
相关文章
文章评论
共有0条评论来说两句吧...