PostgreSQL和MySQL有一个很明显的区别:MySQL里一个实例下的多个库可以直接用库名.表名的方式访问,而PostgreSQL一个连接只能对应一个数据库,想访问同实例下的另一个库,必须重新建立连接。这就带来一个常见问题:业务数据分散在两个库中,其中一个库的函数需要调用另一个库的函数来获取计算结果,怎么办?答案就是dblink扩展。它本质上是一个建立异地连接的管道,让你在当前会话里以SQL的形式操作远端数据库,包括执行远端函数。

一、准备工作:安装并启用dblink扩展
dblink是PostgreSQL官方contrib包里的扩展,大多数发行版在安装数据库时就已经附带。先确认当前库里有没有装过:
SELECT * FROM pg_available_extensions WHERE name = 'dblink'; CREATE EXTENSION IF NOT EXISTS dblink;
如果pg_available_extensions查不到记录,说明服务器端没安装contrib模块。CentOS下可以通过yum install postgresql-contrib补齐,Ubuntu对应postgresql-contrib包,Debian系有时需要手动安装postgresql-contrib-<版本号>。装完后重新执行CREATE EXTENSION即可。
验证扩展是否可用,最简单的办法是试连一下本库:
SELECT dblink_connect('conn1', 'dbname=postgres host=127.0.0.1 port=5432 user=postgres password=xxx');
SELECT dblink_disconnect('conn1');两条语句都成功返回OK,说明扩展已经正常工作。注意连接串里的参数用空格分隔,密码包含特殊字符时建议加上单引号或改用libpq关键词格式,避免解析出错。
二、跨库调用有返回结果集的函数
这是最常见的场景。假设远端库report_db里有一个函数get_user_orders(p_uid int),返回用户订单列表,字段为order_id、amount、created_at。在本地库中调用它的核心写法是dblink配合dblink表函数,并在AS子句中显式声明返回的列结构:
SELECT t.order_id, t.amount, t.created_at
FROM dblink(
'dbname=report_db host=127.0.0.1 port=5432 user=reader password=xxx',
'SELECT order_id, amount, created_at FROM get_user_orders(1001)'
) AS t(order_id int, amount numeric, created_at timestamp);这里有个非常关键的细节:AS t(...)部分不能省略。dblink拿到远端结果是裸数据流,它不知道每一列的类型,必须由调用方声明列名和类型。如果声明的类型和远端实际类型不匹配,轻则报错,重则数据错乱,所以在写之前最好先到远端执行\df+ get_user_orders确认返回结构。
如果函数返回的是单行结果,比如get_user_summary(uid)返回总金额和订单数,写法完全一样,只是结果只有一行。还有一种更省事的方式是把远端函数包装成本地的视图,后续查询就和普通表没有区别:
CREATE VIEW v_user_summary AS
SELECT * FROM dblink(
'dbname=report_db host=127.0.0.1 user=reader password=xxx',
'SELECT uid, total_amount, order_count FROM get_user_summary(1001)'
) AS t(uid int, total_amount numeric, order_count int);不过要注意,视图里写死了用户ID,实际生产中更多是把这类调用封装成本地函数,参数透传给远端,灵活度更高。
三、调用返回RECORD或SETOF复合类型的函数
当远端函数返回SETOF record这种未定结构类型时,处理方式略有不同。dblink层面其实没有变化,仍然是把远端的SELECT语句原样发过去执行,本地只需要按实际返回的列结构声明即可。例如远端有动态统计函数:
-- 远端函数定义示意
CREATE FUNCTION dynamic_stats(p_type text)
RETURNSSETOF record AS $$ ... $$ LANGUAGE plpgsql;
-- 本地调用时按真实返回列声明
SELECT * FROM dblink(
'dbname=report_db host=127.0.0.1 user=reader password=xxx',
'SELECT stat_name, stat_value FROM dynamic_stats(''daily'')'
) AS t(stat_name text, stat_value numeric);注意上面SQL里出现了双单引号'',这是SQL标准里转义单引号的方式。远端语句本身是一个字符串参数,内部再出现字符串字面量就必须双写,这是跨库调用最容易踩的坑之一,拼接复杂参数时建议用format函数配合quote_literal来构造,避免手工拼接出错。
如果不想反复写连接串,可以先建立命名连接再用dblink('连接名', 'SQL')的两参数形式,连接在会话期间复用,能减少握手开销:
SELECT dblink_connect('rpt', 'dbname=report_db host=127.0.0.1 user=reader password=xxx');
SELECT * FROM dblink('rpt', 'SELECT stat_name FROM dynamic_stats(''daily'')')
AS t(stat_name text);
SELECT dblink_disconnect('rpt');四、调用无返回值的函数与性能注意事项
对于远端的清理类、维护类函数,返回值是void或者你根本不关心结果,用dblink_exec更合适,它的开销比查询型dblink小:
SELECT dblink_exec(
'dbname=report_db host=127.0.0.1 user=reader password=xxx',
'SELECT refresh_materialized_view_concurrently(''mv_daily_stats'')'
);返回值为OK或具体的错误状态字符串。如果函数内部有事务性操作,dblink_exec默认在远端以隐式事务执行,语句成功即提交。需要本地和远端保持一致的多库事务时,dblink原生并不支持两阶段提交,要么业务上容忍最终一致,要么借助外部事务管理器,这一点在设计跨库写操作前必须想清楚。
性能方面有几点经验值得分享。第一,每次调用不具名的dblink都会新建TCP连接、认证、执行、断开,循环里逐条调用的代价非常高,能批量就把参数打包成一次调用,或者复用命名连接。第二,远端函数如果执行时间很长,dblink没有默认超时,建议在连接串里加上connect_timeout=5,必要时配合statement_timeout参数控制。第三,安全上尽量避免把明文密码写进SQL,可以借助.pgpass文件或外部表foreign data wrapper方案,后者在PostgreSQL 11之后用postgres_fdw加用户映射的方式管理凭据,比dblink更适合长期稳定的跨库访问需求。
总结一下,dblink调用远端函数的核心就三点:连接串写对、AS子句声明准、引号转义仔细。掌握这些,跨库取数和跨库触发计算都能顺利落地。如果只是读远端表数据且场景固定,也可以评估postgres_fdw,它对远端函数的支持同样是通过SQL下推实现的,架构上更干净。
PostgreSQLdblink跨数据库查询修改时间:2026-09-07 12:30:41