一、FDW简单理解
FDW (foreign-data wrapper,外部数据包装器),PostgreSQL FDW 是一种外部访问接口,它可以被用来访问存储在外部的数据,这些数据可以是外部的pg数据库,也可以oracle、mysql等数据库,甚至可以是文件。
可以让我们在PostgreSQL 中使用SQL查询极为丰富的外部数据:
主流关系型数据库:Oracle、MySQL、SQL Server等
NoSQL数据库:ClickHouse、MongoDB、Redis、Neo4j等
外部文件:csv、josn、pg_dump、xml
Web文件:S3、Twitter、Facebook…
fdw外部数据源
二、FDW连接外部数据源相关使用
简单来说,通过在PG库里建立其他数据库的外部表或者视图的方式,访问其他数据库表数据的方式就是FDW。
安装:
处理外部数据源的插件(每类数据库各有不同,需要分别安装):
- 使用yum安装的,需要执行yum install postgresql-contrib安装对应版本包
- 使用源码安装的;
1. Extension
--创建extension
create extension postgres_fdw;
--查询extension对应视图
select * from pg_foreign_data_wrapper;
--查看extension插件
select * from pg_extension;
--删除extension
drop extension postgres_fdw;
2. Server
连接目标库(要访问的IP、端口、DB名)
--创建server
CREATE SERVER foreign_server
FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host 'IP', port '端口', dbname 'DB名');
--查询对应server视图
select * from pg_foreign_server;
或者
\des
--删除server
drop SERVER foreign_server;
3. User Mapping
用户映射/账号映射,目标库使用的账号/密码(可以单独创建指定专用用户,也可以用现有用户);
--创建User Mapping
CREATE USER MAPPING FOR postgres
SERVER foreign_server
OPTIONS (user '账号', password '密码');
--查看User Mapping视图
select * from pg_user_mappings;
--删除User Mapping
DROP USER MAPPING for user_name SERVER foreign_server;
4. Foreign Table
本地外部表对应目标库哪张表或视图,外部表字段可以少于目标表和视图,按需索取;
--创建外部表、视图
CREATE FOREIGN TABLE fdw_foreign_table(
id integer NOT NULL
)
SERVER foreign_server
OPTIONS (
schema_name 'public',
table_name 'foreign_table'
);
-- 查询外部表对应视图
select * from pg_foreign_table;
--删除外部表、视图
drop FOREIGN TABLE fdw_foreign_table;
5. 导入表定义
pg .11开始,可以用下面语句导入表定义
--导入全表
IMPORT FOREIGN SCHEMA foreign_films
FROM SERVER film_server INTO films;
--只导入部分字段
IMPORT FOREIGN SCHEMA foreign_films LIMIT TO (id, name)
FROM SERVER film_server INTO films;
6.查询外部表
pg .14开始,如果外部用户有权限,现在可以对外部表执行INSERT、UPDATE、DELETE、COPY、TRUNCATE
操作。对于INSERT
,目前不支持ON CONFLICT DO UPDATE
子句,但支持了CONFLICT DO NOTHING
子句。
--查询外部表结构
\d fdw_foreign_table
--查询外部表数据
select * from fdw_foreign_table;
7. 外部连接及状态
--返回postgres_fdw建立的外部连接及状态
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
--断开指定连接
SELECT postgres_fdw_disconnect('xxx');
--断开所有连接
SELECT postgres_fdw_disconnect_all();
三、连接其他数据库
1. mysql_fdw
create extension mysql_fdw;
CREATE SERVER mysql_server
FOREIGN DATA WRAPPER mysql_fdw
OPTIONS (host 'ip', port '端口', dbname 'DB名');
CREATE USER MAPPING FOR pg_rw
SERVER mysql_server
OPTIONS (user '账号', password '密码');
CREATE FOREIGN TABLE fdw_mysql_mytab (
id int,
name character varying(128)
)
SERVER mysql_server
OPTIONS (
dbname 'DB名',
table_name 'table名'
);
2. file_fdw
将csv格式文件创建为外部表,file_fdw不需要用户映射。
create extension file_fdw;
CREATE SERVER log_server FOREIGN DATA WRAPPER file_fdw;
CREATE FOREIGN TABLE postgres_log
(
log_time timestamp,
user_name text,
database_name text,
process_id integer,
connection_from text,
session_id text,
session_line_num bigint
) SERVER log_server
OPTIONS (format 'csv', header 'false', filename '/data/postgresql-01.csv', delimiter ',', null'');
四、FDW原理
实现一个FDW的核心是实现一组回调函数,有了这些回调函数的帮助, 在查询外部表对象的执行过程中就可以将运行逻辑切换至自定义的扩展代码中, 进而遵照PG的内部机制实现对外部数据源的访问。
目前PostgreSQL11 beta2,提供的FDW回调函数接口有39个。FDW的实现者需要根据外部数据源自身的能力(比如是否支持写操作,以及是否支持在外部数据源端执行join操作等等)对这些接口有选择地予以实现。
这些接口中, 最核心的接口有7个。无论外部数据源自身能力如何, 这7个接口是实现通过外部表对象访问该数据源的必须接口。它们的接口定义如下:
typedef void (*GetForeignRelSize_function) (PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid);
typedef void (*GetForeignPaths_function) (PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid);
typedef ForeignScan *(*GetForeignPlan_function) (PlannerInfo *root, RelOptInfo *baserel,Oid foreigntableid, ForeignPath *best_path, List *tlist, List *scan_clauses, Plan *outer_plan);
typedef void (*BeginForeignScan_function) (ForeignScanState *node, int eflags);
typedef TupleTableSlot *(*IterateForeignScan_function) (ForeignScanState *node);
typedef void (*ReScanForeignScan_function) (ForeignScanState *node);
typedef void (*EndForeignScan_function) (ForeignScanState *node);
在PG中,查询语句经过以下5个子系统处理:
- Parser
用于将文本式的SQL命令转换成解析树 - Analyzer/Analyser
用于执行解析树的语义分析并生成查询树 - Rewriter
重写器用于根据规则系统中已存在的规则转换查询树 - Planner
规划器生成可以从查询树中最有效地执行的计划树 - Executor
执行器通过按计划树创建的顺序访问表和索引来执行查询
可以整合成三个大阶段:
- Parser: 包含对SQL的语法解析,语义校验,查询重写;
- Optimizer:生成查询计划;
- Executor:按照火山模型执行查询计划的算子并向上返回数据;
PG的FDW所需的7个回调函数主要是在Optimizer和Executor阶段进行“介入”:
-
GetForeignRelSize:
- 在PG中的调用时机:优化器生成访问路径的过程中对外部表估算访问代价时;
- 作用:提供外部表对于计算访问代价所需的基础数据,如表的元组数以及元组的平均长度,并将这些数据保存在输入参数baserel的字段”rows”以及”width”中。
- 详细描述:
void GetForeignRelSize (PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid);
root是规划器的关于该查询的全局信息;baserel是规划器的关于该表的信息;foreigntableid是外部表在pg_class中的 OID (foreigntableid可以从规划器的数据结构中获得,但是为了减少工作量,这里直接显式地将它传递给函数);
这个函数应该更新baserel->rows为表扫描根据限制条件完成了过滤后将返回的预期行数。baserel->rows的初始值只是一个常数的默认估计值,应该尽可能把它替换掉。如果该函数能够计算出一个平均结果行宽度的更好的估计值,该函数也可能选择更新baserel->width。
-
GetForeignPaths
- 在PG中的调用时机:生成对外部表的访问路径时;
- 作用:生成对目标外部表的访问路径(通过PG中的接口createforeignscanpath()生成);
- 详细描述:
void GetForeignPaths (PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid);
参数和GetForeignRelSize相同;
这个函数必须为外部表上的扫描生成至少一个访问路径(ForeignPath节点),并且必须调用add_path把每一个这样的路径加入到baserel->pathlist中。我们推荐使用create_foreignscan_path来建立ForeignPath节点。该函数可以生成多个访问路径,例如一个具有合法pathkeys的路径表示一个预排序好的结果。每一个访问路径必须包含代价估计,并且能包含任何FDW的私有信息,这种信息被用来标识想要使用的指定扫描方法。
-
GetForeignPlan
- 在PG中的调用时机:优化器生成扫描外部表的查询计划节点时;
- 作用:生成访问目标外部表的ForeignScan计划节点(通过PG中的接口make_foreignscan());
- 详细描述:
ForeignScan * GetForeignPlan (PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid, ForeignPath *best_path, List *tlist, List *scan_clauses, Plan *outer_plan);
参数和GetForeignRelSize的一样,外加选中的ForeignPath(在前面由GetForeignPaths、GetForeignJoinPaths或者GetForeignUpperPaths产生)、被计划节点发出的目标列表以及计划节点强制的限制子句以及被RecheckForeignScan执行的复查所使用的ForeignScan的外子计划(如果该路径是用于一个连接而非基本关系,则foreigntableid是InvalidOid);
这个函数必须创建并返回一个ForeignScan计划节点,推荐使用make_foreignscan来建立ForeignScan节点。
-
BeginForeignScan
- 在PG中的调用时机:执行器即将开始执行ForeignScan算子,进行该算子相关的初始化时;
- 作用:获取执行ForeignScan算子所需的信息,并将它们组织并保存在ForeignScanState中;
- 详细描述:
void BeginForeignScan (ForeignScanState *node, int eflags);
它应该执行任何在扫描能够开始之前需要完成的初始化工作,但是并不开始执行真正的扫描(会在第一次调用IterateForeignScan时完成)。ForeignScanState节点已经被创建好了,但是它的fdw_state属性仍然为 NULL。关于要被扫描的表的信息可以通过ForeignScanState节点访问(特殊地,从底层的ForeignScan计划节点,它包含任何由GetForeignPlan提供的FDW私有信息)。eflags包含描述执行器对该计划节点操作模式的标志位。
注意当(eflags & EXEC_FLAG_EXPLAIN_ONLY)为真时,这个函数不应该执行任何外部可见的动作;它应当只做最少的事情来创建对ExplainForeignScan 和EndForeignScan有效的节点状态
-
IterateForeignScan
- 在PG中的调用时机:执行ForeignScan算子过程中需要获取下一元组时;
- 作用:读取外部数据源的一行数据,并将它组织为PG中的Tuple(即TupleTableSlot). 当该回调函数返回一个空的TupleTableSlot结构时, 迭代器停止迭代。
- 详细描述:
TupleTableSlot * IterateForeignScan (ForeignScanState *node);
从外部源获得一行,将它放在一个元组表槽中返回(节点的ScanTupleSlot应当被用于此目的)。如果没有更多的行可用则返回 NULL。元组表槽设施允许一个物理的或者虚拟的元组被返回;在大部分情况下出于性能的考虑会倾向于选择后者。注意这是在一个短期存在的内存上下文中被调用的,该内存上下文会在调用之间被重置。如果需要长期存在的存储,需要在BeginForeignScan中创建内存上下文,或者使用节点的EState中的es_query_cxt。
如果提供了fdw_scan_tlist目标列表,被返回的行必须匹配它,如果没有提供则它们必须匹配被扫描的外部表的行类型。如果选择优化掉不需要的列,你应该在那些列的位置上插入控制或者生成一个忽略了那些列的fdw_scan_tlist列表。
注意PostgreSQL的执行器并不在乎被返回的行是否违背了定义在该外部表上的任何约束 — 但是规划器会在乎这一点,并且如果在外部表中有可见行不满足一个约束,规划器可能会错误地优化查询。如果当用户已经声明一个约束应该为真时它却被违背,最合适的处理可能是产生一个错误(就像在数据类型失配的情况下所作的那样)
-
ReScanForeignScan
- 在PG中的调用时机:执行Nested Loop过程中需要重置Inner Scan时(即Outter Scan需要向前推进一行时);
- 作用:将外部数据源的读取位置重置回最初的起始位置;
- 详细描述:
void ReScanForeignScan (ForeignScanState *node);
注意扫描所依赖的任何参数可能已经改变了值,因此新扫描不一定会返回完全相同的行。
-
EndForeignScan
- 在PG中的调用时机:ForeignScan算子执行完成时;
- 作用:释放整个ForeignScan算子执行过程中占用的外部资源或FDW中的资源;
- 详细描述:
void EndForeignScan (ForeignScanState *node);
通常释放palloc过的内存并不重要,但是打开的文件和到远程服务器的连接等应该被清理。