第10章 复制与外部数据

第10章 复制与外部数据

PostgreSQL 有很多方法可以实现与外部服务器或数据源之间的数据共享。第一种就是 PostgreSQL 自带的复制功能,通过该功能可以在另外一台服务器上创建出当前服务器的一个镜像。第二种方法是使用第三方插件,其中许多插件可以免费使用,并且其可靠性也是久经考验的。第三种方法是使用外部数据封装器FDW。FDW 支持大量的外部数据源,从 9.3 版开始,有些 FDW 也开始支持对外部数据进行修改,包括 postgres_fdw、hadoop_fdw 和 ogr_fdw 等。

10.1 复制功能概览

确保可用性的手段是提供一台冗余的备用服务器,如果主服务器宕机了,备用服务器应立即接管并继续提供服务。对于规模较小的数据库来说,要达到此目标只需要保证你有另一台备用的物理服务器,并将数据库恢复到该服务器上即可。但对于规模很大的数据库(数据量为 TB 级别)来说,恢复过程本身可能需要好几个小时甚至几天,而且在此过程中系统无法对外提供服务。为尽量减少服务中断时间,你就需要使用复制功能。

10.1.1 复制功能涉及的术语

主服务器
  主服务器是作为要复制数据的源头的数据库服务器,所有更新都在其上发生。

从属服务器
  从属服务器使用复制的数据并提供主服务器的副本。

预写日志(write-ahead log,WAL)
  WAL 就是记录所有已完成事务信息的日志文件,在其他数据库产品中一般称为事务日志。为了支持复制功能,PostgreSQL 将主服务器的 WAL 日志向从属服务器开放,然后从属服务器持续地将这些日志取到本地,然后将其中记载的事务重演一遍,这样就实现了数据同步。

同步复制
  在事务提交阶段,PostgreSQL 需保证已经将此事务中所做的修改成功同步到 synchronous_stand_names 参数中所列出的至少一个从属服务器上,然后才能向用户反馈事务提交成功。在 PostgreSQL 9.6 之前,只需任何一个同步服务器反馈成功,事务就算成功。在 PostgreSQL9.6 以及之后的版本中支持了一个增强功能,即可以要求至少 N 个从属服务器反馈成功后整个事务才算成功,这个 N 可以在 postgresql.conf 的 synchronous_standby_names 参数中进行配置。PostgreSQL 10 更进一步支持了 FIRST 和 ANY 这两个关键字,分别代表“只需第一个从属服务器反馈成功”和“只需任何一个从属服务器反馈成功”,这两个关键字也是在synchronous_standby_names 参数中进行配置。如果不配置该项,则默认值为 FIRST,即只需第一个从属服务器反馈成功,PostgreSQL 9.6 在默认情况下的处理策略也是如此。

异步复制
  在事务提交阶段,主服务器上提交成功就算成功,不需要等待从属服务器的数据更新成功。当从属服务器位于远端时该模式就比较有用了,因为可以避免网络延迟的影响。但有利必有弊,该模式下从属服务器的数据更新不够及时,与主服务器之间会有一些延迟。如果主从服务器之间的延迟很严重,以至于主从之间的差距大到主服务器上的 WAL 事务日志还没有传输到从属服务器就已经被清理掉,那么此时其实复制环境已经被破坏且无法恢复,从属服务器需要基于主服务器的数据重新初始化一遍。

为尽量降低出现上述问题的风险,PostgreSQL 9.4 中引入了复制槽(replication slot)的概念。所谓“复制槽”是指主从服务器之间的一种契约,该契约保证了在从属服务器消费到相应的 WAL 日志之前,这部分 WAL 日志不会被主服务器删除。这么做又会导致另一个风险,就是如果某个持有“复制槽”的从属服务器发生故障或者与主服务器之间通信中断,那么主服务器上就不得不永久保留那些古老的 WAL日志(因为不确定从属服务器是否已经消费这部分 WAL,所以不能删),从而导致磁盘空间被占满,最后导致服务器重启。

流式复制
  流式复制模式下,WAL 日志并不是通过直接复制文件的方式从主服务器传递到从属服务器,而是通过基于 PostgreSQL 内部协议的消息来传递的。

级联复制
  一个从属服务器可以把 WAL 日志传递给另一个从属服务器,而不需要所有的从属服务器都从主服务器取 WAL 日志,这进一步减轻了主服务器的负担。这种模式下,有的从属服务器可以作为同步的数据源,从而继续向别的从属服务器传播 WAL 数据,从这个角度看,其作用类似于主服务器。注意,这种扮演着“WAL 日志二传手”角色的从属服务器是只读的,它们也被称为级联从属服务器。

逻辑复制
  这是 PostgreSQL 10 新支持的复制功能,通过该功能可以实现仅复制若干特定的表,而无须复制整个 PostgreSQL 服务器的所有数据,从而大大增加了复制的灵活性。该功能的实现依赖于一个名为逻辑解码(logic decoding)的机制,该机制可以将表数据的变更历史从 WAL 日志中以一种易于理解的格式解析出来,过程中无须关注数据库 WAL 日志格式的内部实现细节。逻辑解码机制从 PostgreSQL 9.4 开始就已支持,某些扩展包基于它实现了审计和复制功能。使用逻辑复制功能时,需要用到 CREATE PUBLICATION 和 CREATE SUBSCRIPTION 这两个 DDL 语法,前者表示哪些表要进行复制,后者表示哪些 PostgreSQL 服务器上的哪些 database 要订阅数据。
  
  另外请注意,要使用该功能,需要把 wal_level 参数的值设置为 logical。

重新选主
  重新选主是指从所有的从属服务器中选择一个并将其身份提升为主服务器的过程。PostgreSQL 9.3引入了基于流消息复制的选主机制,该模式下选主时仅依靠流消息,而不再需要访问 WAL 日志文件,同时从属服务器也不需要经历一次重新复制过程。直到 9.4 版为止,重新选主还会要求整个数据库服务重启一次,将来的版本中可能会改进这一点。

PostgreSQL 的复制机制会复制所有事务性的变更,即在事务内发生的一切都可以被复制到从节点。PostgreSQL 中所有的 DDL 操作都是事务性的,因此建表、建视图、安装扩展包之类的操作都是可被复制的。注意非日志(unlogged)表上的插入、更新和删除操作是不记录 WAL 日志的,因此这些操作不会被复制。所以当在主节点上安装扩展包时,要确保从节点上有扩展包的安装包,而且版本号需要和主节点一致,否则在主节点上执行 CREATE EXTENSION 操作会失败

10.1.2 复制机制的演进

对复制机制的支持在以下 PostgreSQL 版本中不断演进。

  • 9.4 版中新增了对复制槽的支持。所谓“复制槽”是指主从服务器之间的一种契约,该契约保证了在从属服务器尚未消费到相应的 WAL 日志之前,这部分 WAL 日志不会被主服务器删除。
  • 9.5 版中引入了若干用于监控复制过程的函数,详情请参考官方手册中的“复制进度跟踪”部分。
  • 9.6 版中支持了在同步模式下设置多个同步备节点的能力,目的是为了提升可靠性。
    PostgreSQL 10 中支持了原生的逻辑复制功能,该功能使得仅复制指定的若干张表成为可能。逻辑复制功能的另一个好处是可以让从节点有自己专属的表,这些表不参与复制,可以在从节点上对其进行任意修改。

尽管逻辑复制是 PostgreSQL 10 才原生支持的新特性,但其实从 PostgreSQL 9.4 开始用户已经可以通过开源的 pglogical 扩展包来实现逻辑复制能力。如果你需要在不同的 PostgreSQL 大版本间进行复制,比如 PostgreSQL 10 和 PostgreSQL 9.4~9.6 之间,可以通过在两端的 PostgreSQL 服务器上都安装相应版本的 pglogical 扩展包来实现。如需在 PostgreSQL 10 和将来的新版本间进行逻辑复制,则无须借助 pglogical,直接使用原生的逻辑复制能力即可。

10.1.3 第三方复制解决方案

第三方提供的复制工具,Slony 和 Bucardo 是其中应用最广泛的两个,而且都是开源的。尽管 PostgreSQL 原生复制机制在每个版本中都会得到功能强化,但 Slony、Bucardo 以及其他第三方工具仍然在灵活性方面有着原生复制机制难以比拟的优势:它们支持仅复制单个 database 或者单张表;它们也不要求复制的源端和目的端的 PostgreSQL 版本和操作系统相同;它们还支持多主复制。但它们也有缺点:两个工具均依赖于新建额外的触发器来触发复制动作,同时还可能需要在被复制表上增加一些额外的字段,因此它们对系统架构有一定的侵入性;并且它们一般不支持建表、安装扩展包等 DDL 操作的同步。可以看出,这些第三方解决方案相比原生复制方案需要更多的人工干预,比如建触发器,为表加字段或者创建额外的视图,等等。

10.2 复制环境的搭建

使用流式复制模式来实现,该模式基于主服务器和从属服务器之间的数据库连接来进行 WAL 日志传输。

10.2.1 主服务器的配置

(1) 创建一个专用于复制的用户账号。

CREATE ROLE pgrepuser REPLICATION LOGIN PASSWORD 'woohoo';

(2) 在 postgresql.conf 中设置好以下配置项。也有一种无须直接修改配置文件的方法:使用 ALTER SYSTEM set 参数名 = 参数值来修改配置项值,改好以后执行 SELECT pg_reload_conf() 来让配置生效。

listen_addresses = *
wal_level = hot_standby
archive_mode = on
max_wal_senders = 5
wal_keep_segments = 10

如果你希望基于逻辑复制来实现部分表的复制,那么要将 wal_level 设置为 logical。logical 模式相比 hot_standby 模式会记录更多事务日志,因此 logical 模式既适用于整个 PostgreSQL 服务器复制场景,又适用于指定部分表复制的场景。

如果主从服务器的距离很远,而且主服务器上的事务处理又很繁忙,那么我们建议把 wal_keep_segments 参数设得大一点。如果运行的是 PostgreSQL 9.6 或更高版本,应该将 wal_level 设置为 replica,而不是上面写的 hot_standby。为了后向兼容,PostgreSQL 9.6 中会将 hot_standby 自动当成 replica 处理。

(3) 在 postgresql.conf 中添加 archive_command 配置指令,或者用 ALTER SYSTEM 修改,该参数的含义是 WAL 日志的保存路径。在流式复制模式下,该配置指令中的目标路径可设定为任何路径。

archive_command = 'cp %p ../archive/%f'

也可以使用 rsync 命令替代 cp 以实现异地归档:

archive_command = 'rsync -av %p postgres@192.168.0.10:archive/%f'

(4) 在 pg_hba.conf 文件中设置一条权限规则,以允许从属服务器作为复制体系中的客户端连到主服务器。

host replication pgrepuser 192.168.0.0/24 md5

(5) 重启 PostgreSQL 服务来让所有配置生效。使用 PostgreSQL 安装路径下的 bin 文件夹中的 pg_basebackup 工具,来为整个 PostgreSQL 服务器创建一个全量备份。备份结果包含指定目录下的全量数据文件的副本。使用 pg_basebackup 工具时,如果加上了 --xlog-method-stream 参数,则会把所有 WAL 日志也备份下来,过程中会建立一个数据库连接用于进行 WAL 复制;如果加上了 -R 参数,则会自动创建一个用于恢复的配置文件。

在 PostgreSQL 10 中,原先的 pg_xlog 目录已经被重命名为 pg_wal。

登录到从属服务器并针对主服务器(192.168.0.1)做了一次流式全量备份:

pg_basebackup -D /target_dir -h 192.168.0.1 \
--port=5432 --checkpoint=fast
--xlog-method=stream -R

如果你是为了实现备份的目的而使用 pg_basebackup,那么可以使用先打 TAR 包再做压缩这种常见输出形式,最终会在备份目标目录下为每个表空间生成一个 tar.gz 文件。注意,下面的命令行中的 -X等同于 --xlog-method。由于流式日志不支持以压缩格式备份,所以需要把取日志的方式改为文件传递,命令行如下:

pg_basebackup -Z9 -D /target_dir/ -h 192.168.0.1 -Ft -Xfetch

除了备份全量数据外,我们一般还会将 WAL 日志也纳入备份体系中。为了实现这一点,在 PostgreSQL10 之前,需要使用 pg_receivexlog 工具来获取事务日志,在 PostgreSQL 10 以及之后的版本中,该工具被改名为 pg_receivewal。只需为该工具设置一个定时任务,即可实现持续地导出并备份事务日志。

10.2.2 为从属服务器配置全量复制环境

建议从属服务器与主服务器的各项系统配置完全相同,这会为你减少很多麻烦,特别是当你需要搭建一套用于保障系统高可用的主备倒换环境时,这一点尤其重要。此外,如果要在主服务器上安装扩展包,那么从节点上必须有相应扩展包的二进制安装文件,否则在主节点上执行 CREATE EXTENSION 后,从节点上执行恢复过程时就会报错。从属服务器节点必须能够处理主服务器发来的 WAL 事务日志。

(1) 新建一个数据库实例作为从属服务器,要求采用与主服务器相同的 PostgreSQL 版本(最好是小版本号也完全相同)。
(2) 关闭从属服务器的 PostgreSQL 服务。
(3) 使用 pg_basebackup 导出的文件覆盖从属服务器上的相应文件。
(4) 将下面的配置设置添加到 postgresql.auto.conf 文件中。

hot_standby = on
max_connections = 20 #set to higher or equal to master

(5) 从属服务器的侦听端口不必与主服务器一样,因此可以选择在 postgresql.auto.conf 或者 postgresql.conf 中更改端口,也可以通过其他特定于操作系统的启动脚本进行更改,这些启动脚本会在启动之前设置 PGPORT 环境变量。
(6) 在 data 文件夹下创建一个名为 recovery.conf 的新文件,内容如下(注意下面第二行中要修改为真实的主机名、IP 地址和端口)。如果前面使用了 pg_basebackup 进行全量导出,那么该文件已自动生成,只需手动加上 trigger_file 那一行即可。

standby_mode = 'on'
primary_conninfo = 'host=192.168.0.1 port=5432 user=pgrepuser
password=woohoo application_name=replica1'
trigger_file = 'failover.now'

(7) 如果发现从属服务器处理事务日志的速度较慢,跟不上主服务器产生日志的速度,为避免主服务器产生积压,你可以在从属服务器上指定一个路径用于缓存暂未处理的日志。请在 recovery.conf 中添加如下一个代码行,该代码行在不同操作系统下会有所不同。

restore_command = 'cp %p ../archive/%f'

10.2.3 启动流复制进程

如果已经用 pg_basebackup 完成了数据全量导出,那么请查看一下其中的 recovery.conf 文件的内容,确认是否均正常,然后再启动从属服务器。

如果希望某个从属服务器脱离当前的主从复制环境,即此后以一*立的 PostgreSQL 服务器身份而存在,请直接在其 data 文件夹下创建一个名为 failover.now 的空文件。从属服务器会在处理完当前接收到的最后一条事务日志后停止接收新的日志,然后将 recovery. conf 改名为 recovery.done。此时从属服务器已与主服务器彻底解除了复制关系,此后这台 PostgreSQL 服务器会作为一*立的数据库服务器存在,其数据的初始状态就是它作为从属服务器时处理完最后一条事务日志后的状态。一旦从属服务器脱离了主从复制环境,就不可能再切换回主从复制状态了。要想切换回去,必须按照前述步骤一切从零开始。

10.2.4 使用逻辑复制实现部分表或者部分database的复制

逻辑复制的一大优势是支持在 PostgreSQL 10 和未来更新的版本间进行复制,而且不需要主从节点的操作系统和硬件架构相同。例如,我们可以实现一台 Linux 的 PostgreSQL 服务器与一台Windows 的 PostgreSQL 服务器之间的复制。

在逻辑复制的概念体系中,数据提供方的服务器被称为“发布者”(publisher),数据接收方的服务器被称为“订阅者”(subscriber)。在发布者服务器上需复制的表所在的 database 中执行 CREATE PUBLICATION,即可将待复制的表作为数据源发布出去;在订阅者服务器上要接收数据的 database 中执行 CREATE SUBSCRIPTION,来指定要从哪个发布者服务器的哪个数据源来订阅数据。逻辑复制的主要问题在于它不支持 DDL 复制,因此要求待复制的表在开始复制之前在主从服务器都必须先建好。

(1) 在发布端 PostgreSQL 实例上确保以下参数已配置:

SHOW wal_level

如果显示的值不是 logical,那么请使用以下命令修改:

ALTER SYSTEM SET wal_level = logical;

然后重启 PostgreSQL 服务。

如果需要实现级联复制,即订阅端服务器也需要作为发布者来向下一级订阅者进行发布,那么在订阅端服务器上最好也把 wal_level 配置成 logical。

(2) 在订阅端服务器上的目标 database 中创建待复制的表。如果你有许多表要复制或者要复制发布端某个 database 的所有表,那么建议你使用 pg_dump 将待复制的表结构导出来,然后拿到订阅端服务器上执行。例如,如果需要复制 postgresql_book 这个 database,通过以下语句可以导出其结构:

pg_dump -U postgres -p5447 -Fp --section pre-data --section post-data \
-f pub_struct.sql postgresql_book

然后用 psql 连接到订阅端服务器上执行前面导出的脚本,命令如下:

CREATE DATABASE book_sub;
\connect book_sub;
\i pub_struct.sql

(3) 然后在发布端服务器上针对待复制的 database 创建一个数据源。我们在这个例子中将使用 CREATE PUBLICATION 来实现对某个 database 中所有表的复制。请注意,以下命令针对将来在此 database 中建立的表也生效,但要想这些后建的表能复制成功,需要先到订阅端服务器上把这些新表也创建一下:

CREATE PUBLICATION full_db_pub
FOR ALL TABLES;

(4) 要想刚刚建立的发布数据源起作用,需要对其进行订阅消费。连接到订阅端服务器上的 book_sub 这个 database 中,然后执行以下命令:

\connect book_sub;
CREATE SUBSCRIPTION book_sub
CONNECTION 'host=localhost port=5447 dbname=postgresql_book \
user=postgres'
PUBLICATION full_db_pub;

以上命令执行完毕后,查看一下订阅端 book_sub 库中的表,可以看到其中已经有了首次同步复制过来的数据。如果在发布端的 postgresql_book 库中插入新的记录,可以看到 book_sub 中已经复制过来了。

如果不再需要某个发布端或者订阅端,可以使用 DROP SUBSCRIPTION 和 DROP PUBLICATION 删除它们。

10.3 外部数据封装器

FDW 的核心概念是“外部表”,这种表看起来和当前 PostgreSQL 中其他表的用法完全相同,但事实上其数据本体是存在于外部数据源中的,该数据源甚至可能存在于另外一台物理服务器上。一旦定义好了外部表,其定义就会在当前数据库中持久化,你就可以放心地与使用普通表一样使用它,FDW 完全屏蔽了与外部数据源之间的复杂通信过程。

GitHub 上搜索“PostgreSQL Foreign Data Wrappers”,可以搜索到前述很多 FDW 的源码。

大多数 PostgreSQL 安装包会默认携带两个 FDW:file_fdwpostgres_fdw,但默认没有安装,你可以执行 CREATE EXTENSION 来安装它们。

10.3.1 查询平面文件

可以使用 file_fdw 这个 FDW 来查询平面文件,它是以扩展包的形式存在的,因此可以通过以下 SQL安装:

CREATE EXTENSION file_fdw;

尽管通过 file_fdw 可以直接读取数据库实例所在的本地服务器上的文件,但为了和别的 FDW 在语义上保持一致,还是得定义一个逻辑上的外部服务器。请执行以下命令来定义一个“伪”外部服务器:

CREATE SERVER my_server FOREIGN DATA WRAPPER file_fdw;

接下来要注册外部表。你可以将外部表置于任何一个 schema 中,但我们一般是创建一个单独的 schema来容纳所有的外部表。接下来将使用一个名为 staging 的 schema 。

先列出几行我们将要访问的外部文件的内容,每一行内的字段用管道符分隔

Dev|Company
Tom Lane|Crunchy Data
Bruce Momjian|EnterpriseDB

定义基于分隔符格式文件的外部表

CREATE FOREIGN TABLE staging.devs (developer VARCHAR(150), company VARCHAR(150))
SERVER my_server
OPTIONS (
format 'csv',
header 'true',
filename '/postgresql_book/ch10/devs.psv',
delimiter'|',
null''
);

尽管外部表映射到一个用管道符作为分隔符的平面文件,但我们依然将其标识为“csv”格式。有人可能会根据 CSV的名称认为只有用逗号作为分隔符的文件才能称为 CSV,但在 FDW 的术语体系中,CSV 文件就是以某种分隔符来区分列值的平面文件,不管这个分隔符具体是什么字符,都可以称之为 CSV。

上述定义步骤完成后,你就可以直接通过 SQL 访问外部表了:

SELECT * FROM staging.devs WHERE developer LIKE 'T%';

如果不再需要此外部表,可以删掉:

DROP FOREIGN TABLE staging.devs;

10.3.2 以不规则数组的形式查询不规范的平面文件

通常,平面文件在每一行中会有许多不同的列,并且包含多个标题行和页脚行。我们最喜欢使用 file_textarray_fdw 这个 FDW 来处理这种非结构化平面文件。该 FDW 能处理任何带分隔符的平面文件,即使每一行中的元素数量不一致也没问题,因为它可以将每一行作为一个变长的文本数组(text[])来进行处理。

file_textarray_fdw 不是 PostgreSQL 原生支持的扩展包,因此你必须手动编译安装它。首先,在安装 PostgreSQL 时需要附带安装系统头文件,以便于后续的编译。然后从 Adunstan GitHub 这个站点下载 file_textarray_fdw 的源码。请注意:该站点为每个 PostgreSQL 版本都准备了相应的源码,请确保选择的是正确的版本

FDW 安装好以后,首先创建扩展包。

CREATE EXTENSION file_textarray_fdw;

创建好外部服务器。

CREATE SERVER file_taserver FOREIGN DATA WRAPPER file_textarray_fdw;

设置外部表。可以将外部表放入任何一个你认为合适的 schema 中。

创建一个基于文本数组的外部表

CREATE FOREIGN TABLE staging.factfinder_array (x text[])
SERVER file_taserver
OPTIONS (
format 'csv',
filename '/postgresql_book/ch10/DEC_10_SF1_QTH1_with_ann.csv',
header 'false',
delimiter ',',
quote '"',
encoding 'latin1',
null ''
);

假设我们要处理这样一个 CSV 文件:文件中含有 8 个标题行,而列数多得我们不想数。当前述设置步骤都完成后,就可以直接查询这个文件的内容了。通过以下查询可以得到标题行的名称,这些标题行的第一个列标头为 GEO.id。

SELECT unnest(x) FROM staging.factfinder_array WHERE x[1] = 'GEO.id'

以下查询能查出数据的前两列。

SELECT x[1] As geo_id, x[2] As tract_id
FROM staging.factfinder_array WHERE x[1] ~ '[0-9]+';

10.3.3 查询其他PostgreSQL服务实例上的数据

从 9.3 版开始,大多数的 PostgreSQL 发行包都包含了 postgres_fdw 这个 FDW。通过它还可以对其他 PostgreSQL 服务器上的数据进行读取和修改操作,哪怕两边 PostgreSQL 版本不一致也没关系。

进行 FDW 扩展包的安装。

CREATE EXTENSION postgres_fdw;

然后创建外部服务器。

CREATE SERVER book_server
FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host 'localhost', port '5432', dbname 'postgresql_book');

如果创建好外部服务器后需要更改连接选项或将其添加到外部服务器,则可以使用 ALTER SERVER 命令。比如,如果需要更改你所指向的服务器,可以执行以下代码行。

ALTER SERVER book_server OPTIONS (SET host 'prod');

对于主机、端口和 database 这几项的连接设置的更改只对新建立的会话生效,不会影响已有的会话。原因是会话在开始时建立,此后就一直复用,而不会断开重连。

然后创建一个用户映射关系 ,将远端的某个角色映射到本地的 public 角色。(所谓“用户映射”是指在远端服务器的某个角色和本地服务器的某个角色之间建立对应关系,这样本地角色可以用远端角色的权限来操作远端服务器上的数据。)

CREATE USER MAPPING FOR public SERVER book_server
OPTIONS (user 'role_on_foreign', password 'your_password');

注意,上述映射关系中的远端角色必须是一个已存在的角色,并且有登录权限。这样任何能连到本地数据库的用户都可以连到远端数据库。

创建外部表了。该表可以映射远端表的全部或部分列。

CREATE FOREIGN TABLE ft_facts (
fact_type_id int NOT NULL,
tract_id varchar(11),
yr int, val numeric(12,3),
perc numeric(6,2)
)
SERVER book_server OPTIONS (schema_name 'census', table_name 'facts');

上面的示例仅包含外部表的最基本的选项。默认情况下,映射到远端 PostgreSQL 数据库的外部表都是可更新的,当然前提是映射关系中所使用的远端数据库角色对映射的远端表要有修改权限。

updatable 设置是一个布尔设置,可以在定义外部表或者外部服务器时进行更改。例如,如果想把外部表设定为只读,可以执行以下代码行。

ALTER FOREIGN TABLE ft_facts OPTIONS (ADD updatable 'false');

要将表设置回 updatable 状态,请执行以下代码行。

ALTER FOREIGN TABLE ft_facts OPTIONS (SET updatable 'true');

表级别上的 updatable 属性会替代外部服务器设置。

ALTER FOREIGN TABLE 语句除了可以更改 OPTIONS 之外,还可以添加或者删除列,语法是 ALTER FOREIGN TABLE … DROP COLUMN。

PostgreSQL 9.5 中引入了对 IMPORT FOREIGN SCHEMA 命令的支持,它可以实现外部表的自动创建,从而为用户节省大量时间。不过请注意,并不是所有的 FDW 都支持 IMPORT FOREIGN SCHEMA 能力。每个 FDW 在执行外部表结构导入时都可以设置一些服务端参数。对于 postgres_fdw 来说,支持的参数如下。

import_collate
  是否将远端 PostgreSQL 服务器上的字符排序规则设置也导入到本地外部表。默认为 true。

import_default
  是否将远端 PostgreSQL 服务器上的字段默认值属性也导入到本地外部表中。默认为 false,即本地服务器上的外表字段上没有默认值。当要对外表进行数据插入操作时,默认值是有用的:如果 Insert语句中未包括某字段,则插入结果中会取该字段的默认值,因此理论上应该要把默认值信息复制过来。但有一点请特别注意:如果该字段的默认值是基于一个序列号生成器的自动递增值,则其效果未必符合你的预期,因为本地服务器上得到的序列号和远端服务器上得到的序列号完全可能不一样。

import_not_null
  是否将远端 PostgreSQL 服务器上的字段 NOT NULL 属性导入到本地外部表中。默认为 true。

使用 IMPORT FOREIGN SCHEMA 命令来导入一个 schema 中的所有表结构(注意大小写)

CREATE SCHEMA remote_census;
IMPORT FOREIGN SCHEMA public
FROM SERVER book_server
INTO remote_census
OPTIONS (import_default 'true');

如果只想导入部分表,可以使用 LIMIT TO 或者 EXCEPT 子句。例如,如果只希望导入 facts 和lu_fact_type 这两张表,可以这么写:

IMPORT FOREIGN SCHEMA census
LIMIT TO (facts, lu_fact_types)FROM SERVER book_server INTO remote_census;

如果 LIMIT TO 后面指定的表在远端 PostgreSQL 服务器上不存在,系统会直接忽略掉,不会报错。我们建议在执行完导入外部表操作后进行一次检查,以确保所有你希望导入的表的确均已导入成功。

EXCEPT 与 LIMIT TO 用法类似,只不过二者效果正好相反:EXCEPT 用于指定哪些表不要导入;LIMIT TO 用于指定哪些表要导入。

参数名为 extensions,是 PostgreSQL 9.6 中引入的,它可以用来提升外部服务器的访问性能。要想用上此特性,请按如下语法为现有的 postgres_fdw 外部服务器进行参数设置:

ALTER SERVER census(OPTION ADD extensions 'btree_gist, pg_trgm');

这个 extensions 参数的内容是一个用逗号分隔的扩展包列表,它表示远端 PostgreSQL 服务器上已经安装了哪些扩展包。当 PostgreSQL 要执行的语句的 WHERE 条件中涉及扩展包中定义的数据类型或者函数时,系统会尝试将这些函数调用推送到远端服务器上去执行,这样性能就得以提升。如果未设置 extensions 参数,所有扩展包中的函数都不得不在本地服务器上执行,这就意味着需要先把所有相关的数据从远端服务器上传输到本地,从而大大地增加了要通过网络传输的数据量,速度自然也会受影响。

10.3.4 使用ogr_fdw查询其他二维表形式的数据源

例如,有专门用于查询 MongoDB 数据的 MongoDB FDW,有专门用于查询 Hadoop 数据源的 Hadoop FDW,也有专门用于查询 MySQL 数据源的 MySQL FDWOracle FDW 等等。

有两种 FDW 能够支持多种数据源。第一个是 Multicorn FDW,它事实上是一种支持用户以Python 语言编写自定义 FDW 的 FDW API 平台。在 Linux 上用起来也问题很多,要想用好需要一些技巧。

另一个支持多种数据源的 FDW 就是 ogr_fdw。ogr_fdw 支持很多二维表形
式的数据源,例如电子表格(比如 Excel 或者 LibreOffice 等)软件使用的表单、Dbase 文件、CSV 文件以及其他关系型数据库等。另外,它还支持空间数据,可以从 SQL Server、Oracle 等关系型数据库把数据导入到 PostGIS 并转换为 PostgreSQL 几何类型。如果你希望根据自行编译安装 ogr_fdw,可以从 GitHub 上下载到源码。

从内部实现机制来看,ogr_fdw 其实是利用了“地理空间数据抽象层库”(Geospatial Data
Abstraction Library,GDAL)来实现上述强大功能。因此,在编译或者使用 ogr_fdw 之前,需要先编译并安装好 GDAL 库。GDAL 库历史悠久、功能特性繁多,因此根据不同的编译选项可以编译出多种多样的能力组合。因此请注意:你的 GDAL 库和我的 GDAL 库有可能差别很大。

GDAL 库安装好以后,一般都自带对 Excel 表格、LibreOffice Calc 表格、ODBC 数据源、空间数据 Web 服务数据源的支持。

安装好 ogr_fdw 的二进制安装包以后,连到 PostgreSQL 上要安装 ogr_fdw 的 database,然后执行以下命令即可:

CREATE EXTENSION ogr_fdw;

由于 ogr_fdw 支持多种多样的外部数据源,因此针对不同的数据源来说,外部服务器的含义也是不一样的。比如对于 CSV 文件来说,CSV 文件所在的目录就对应于外部服务器,该目录下的每个 CSV 文件对应于一张独立的外表;对于 Microsoft Excel 和 LibreOffice Calc 来说,一张工作表就对应于一个外部服务器,这张工作表中的每一张表单就对应于一个独立的外表;对于 SQLite 数据库来说,一个 database 就对应于一个外部服务器,其中的每一张表就对应一个独立的外表。

例如,我们把一个 LibreOffice 工作表映射为一个外部服务器,把其中的表单映射为独立的外表:

CREATE SERVER ogr_fdw_wb
FOREIGN DATA WRAPPER ogr_fdw
OPTIONS (
datasource '/fdw_data/Budget2015.ods',
format 'ODS'
);
CREATE SCHEMA wb_data;
IMPORT FOREIGN SCHEMA ogr_all
FROM SERVER ogr_fdw_wb INTO wb_data;

上面的 ogr_all 是一个用来泛指所有 schema 的代称而非真实的 schema 名,它代指了数据源中所有的 schema,其作用是将数据源中所有的表全部导入,不管表属于哪个 schema。值得注意的一点是,有的外部数据源有 schema 概念而有的数据源没有。为了适应各种数据源,当数据源不提供 schema 概念时,ogr_fdw 可以接受一个虚拟的 schema 名(填在上例中 ogr_all 出现的位置),这个虚拟 schema 名其实是表名前缀,也就是说,所有名字符合此前缀的表都被认为属于该虚拟的 schema,从而可以被一次
性导入。例如,如果希望一次性把所有名字以“Finance”开头的表单导入进来,那么可以把上面脚本中的 ogr_all 替换为“Finance”:

CREATE SCHEMA wb_data;
IMPORT FOREIGN SCHEMA "Finance"
FROM SERVER ogr_fdw_wb INTO wb_data;

schema 的名字是区分大小写的,因此如果表单的名字中含有大写字符或者非标准字符,需要在其前后加引号。

例如,对一个存有 CSV 文件的目录创建外部数据源服务器。创建一个名为 ff 的 schema 来容纳外部表。ogr_fdw 会在 ff 这个 schema 中自动针对每个名字以 Housing 开头的 CSV 文件创建外表。脚本如下:

CREATE SERVER ogr_fdw_ff
FOREIGN DATA WRAPPER ogr_fdw
OPTIONS (datasource '/fdw_data/factfinder', format 'CSV');
CREATE SCHEMA ff;
IMPORT FOREIGN SCHEMA "Housing"
FROM SERVER ogr_fdw_ff INTO ff;

假设在上例的目录中有 Housing_2015.csv 和 Housing_2016.csv 这两个文件,那么系统会为它们在 schema ff 中各建一个外表,表名为 housing_2015 和 housing_2016。

ogr_fdw 默认会对外部数据源中的表名和字段名进行转换:所有大写的表名和字段名都会被修改为小写。如果你不希望发生这种转换,可以在 IMPORT FOREIGN SCHEMA 命令中加上一些参数,来使得外部数据源的表名和字段名维持原样不变。例如:

IMPORT FOREIGN SCHEMA "Housing"
FROM SERVER ogr_fdw_ff INTO ff
OPTIONS(launder_table_names 'false', launder_column_names 'false');

10.3.5 查询非传统数据源

不管外界如何风云变幻,PostgreSQL 无须改变自身的核心功能,而是通过 FDW 搭建与这些异构数据库之间沟通的桥梁。我们将展示如何使用 www_fdw 来查询来自 Web 服务的数据。

PostgreSQL 发行包是不附带 www_fdw 的,因此需要自行编译安装。如果你使用的是 Linux/Unix 环境并且已安装了 postsql-dev 这个包,那么编译是很容易的。请从 https://github.com/cyga/www_fdw下载最新版本的 www_fdw 源码。

首先安装 FDW 扩展包。

CREATE EXTENSION www_fdw;

然后创建针对 Google Web 服务的外部服务器。

CREATE SERVER www_fdw_server_google_search
FOREIGN DATA WRAPPER www_fdw
OPTIONS(uri 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0');

www_fdw 默认支持 JSON 格式数据源,因此我们不需要在上述语句的 OPTIONS 修饰符中特地声明数据源格式。此外 www_fdw 还支持 XML 格式数据源

接下来选定至少一个本地角色来创建 FDW 用户映射关系。每个能连到本地库上的用户都应该有权限访问Google 搜索服务器,因此我们将远端数据源的访问权限映射给本地的 public 角色。

CREATE USER MAPPING FOR public SERVER www_fdw_server_google_search;

基于 Google Web 服务数据源创建一个外部表

CREATE FOREIGN TABLE www_fdw_google_search (
q text,
GsearchResultClass text,
unescapedUrl text,
url text,
visibleUrl text,
cacheUrl text,
title text,
content text
) SERVER www_fdw_server_google_search;

前面设置用户映射关系时未指定任何权限,因此需要进行一次授权动作,然后才可以访问外部表。

GRANT SELECT ON TABLE www_fdw_google_search TO public;

我们以 New in PostgreSQL 10 作为关键词进行搜索,并用正则表达式筛选掉返回结果中的 HTML 标签,语句如下所示。

SELECT regexp_replace(title,E'(?x)(< [^>]*? >)','','g') As title
FROM www_fdw_google_search
WHERE q= 'New in PostgreSQL 10'
LIMIT 2;

搜索结果

title
---------------------
PostgreSQL 10 Roadmap
PostgreSQL: Roadmap
(2 rows)
上一篇:利用FDW进行ORACLE到Postgresql的数据迁移


下一篇:彻底理解ReentrantLock