pg_clickhouse 这个扩展的核心哲学只有三个词:pushdown、pushdown、pushdown。把计算推到 ClickHouse 那边执行,Postgres 只负责接收结果——这才是跨库查询该有的样子。v0.3.0 把这套哲学推得更深了:JSON 类型映射补齐、正则表达式行为统一、格式化函数下推,还顺手修了一个 SSRF 漏洞。
JSON 类型映射终于对齐了
之前 HTTP 驱动已经支持把 ClickHouse 的 JSON 类型映射到 PostgreSQL 的 JSONB,但二进制驱动一直缺这块。v0.3.0 补上了——现在两种驱动都能正确处理 ClickHouse JSON → Postgres JSONB 的映射。
反过来也支持了:Postgres 的 json 类型可以映射到 ClickHouse 的 json 类型。不过实际使用中,jsonb 更贴近 ClickHouse JSON 的语义(两者都保留键顺序、都支持嵌套查询),所以除非你有特殊理由,优先用 jsonb。
函数下推:从格式化到正则再到模糊匹配
to_char → formatDateTime
Postgres 的 to_char(timestamp, fmt) 现在能下推到 ClickHouse 的 formatDateTime()。支持的格式符号包括:YYYY、MM、DD、DDD、HH24、HH12、HH、MI、SS、Q、Mon、Dy、AM/PM 及小写变体。只有二进制兼容的格式才会下推,不兼容的仍由 Postgres 本地计算。
正则表达式:用 re2 扩展消除行为差异
这是个容易被忽视但实际很坑的问题:Postgres 用 POSIX 正则,ClickHouse 用 RE2 正则,语法和行为有差异。同一条正则表达式在两边可能给出不同结果。
v0.3.0 的解决方案是下推新的 re2 扩展里的函数。这个扩展在 Postgres 里提供 RE2 风格的正则函数,下推到 ClickHouse 后行为完全一致。Docker 镜像现在已经包含 re2 扩展,开箱即用。
模糊匹配与数组函数
soundex() 和 levenshtein()(来自 fuzzystrmatch 扩展)现在也能下推。intarray 扩展的 idx 函数下推则做了文档补录。加上之前版本已有的 array_cat()、array_to_string() 等数组函数下推,跨库查询时能留在 ClickHouse 执行的函数越来越多了。
文本函数的 UTF-8 修正
length(text) 和 strpos(text, text) 的下推目标从 ClickHouse 的 length/position 改成了 lengthUTF8/positionUTF8。如果你存的是多字节字符(中文、日文等),这个修正很重要——否则按字节计数和按字符计数会给出不同结果。
列名映射与安全修复
column_name 选项
CREATE FOREIGN TABLE 现在支持 column_name 选项,让 Postgres 列名和 ClickHouse 列名可以不同。二进制驱动的集成也修好了。
-- Postgres 端列名叫 user_name,ClickHouse 端叫 username
CREATE FOREIGN TABLE clickhouse_users (
id bigint,
user_name text OPTIONS (column_name 'username'),
created_at timestamptz
) SERVER clickhouse_server
OPTIONS (table 'users');
SSRF 漏洞修复
clickhouse_raw_query() 函数之前对 public 开放了 EXECUTE 权限,存在 SSRF 风险。v0.3.0 移除了这个权限。这也是版本号跳到 0.3 的原因——需要手动升级:
ALTER EXTENSION pg_clickhouse UPDATE TO '0.3';
一个实际的下推查询示例
下面演示如何在 Postgres 中通过 pg_clickhouse 查询 ClickHouse,并利用下推获得更好的性能。假设你已经在 ClickHouse 有一张事件表。
安装与基础配置
-- 1. 安装扩展
CREATE EXTENSION pg_clickhouse;
-- 2. 配置 ClickHouse 外部服务器
CREATE SERVER clickhouse_server
FOREIGN DATA WRAPPER pg_clickhouse
OPTIONS (
host 'clickhouse.example.com',
port '8123', -- HTTP 端口;二进制驱动用 9000
driver 'http', -- 或 'binary'
database 'analytics'
);
-- 3. 创建外部表,利用 column_name 映射和 JSONB 类型
CREATE FOREIGN TABLE ch_events (
event_id bigint,
event_time timestamptz,
event_type text,
payload jsonb, -- 映射 ClickHouse JSON 类型
region text OPTIONS (column_name 'geo_region')
) SERVER clickhouse_server
OPTIONS (table 'events');
利用下推的查询
-- to_char 下推:按季度聚合,计算在 ClickHouse 完成
SELECT to_char(event_time, 'Q') AS quarter,
count(*) AS event_count
FROM ch_events
WHERE event_time >= '2024-01-01'
GROUP BY quarter
ORDER BY quarter;
-- JSONB 访问器下推:直接在 ClickHouse 解析 JSON
SELECT event_type,
payload->>'action' AS action,
count(*) AS cnt
FROM ch_events
WHERE payload->>'action' IS NOT NULL
GROUP BY event_type, action;
-- re2 正则下推:行为与 ClickHouse 一致
-- 需先安装 re2 扩展:CREATE EXTENSION re2;
SELECT re2_matches(event_type, '^click_.*') AS is_click_event,
count(*)
FROM ch_events
GROUP BY is_click_event;
这些查询中的函数和过滤条件会被推到 ClickHouse 执行,Postgres 只拿到聚合后的少量结果行,网络传输和本地计算都大幅减少。
HTTP 流式传输的内存改进
之前版本的文章还展示了 HTTP 结果集流式传输的改进。老版本会把整个结果集加载到内存再解析,新版本的 HTTP 驱动改为流式处理,内存占用大幅下降。对于返回大量行的查询,这个改进尤其明显。
下推行为的收紧
v0.3.0 还做了一件容易被忽略的事:移除了从 postgres_fdw 继承的"自动下推内置函数"行为。以前,只要 Postgres 和 ClickHouse 有同名函数,就可能被自动下推——但同名不代表语义相同,这会引发静默的错误结果。
现在所有可下推的内置函数都必须显式映射。这意味着下推更安全了,但也意味着某些之前"碰巧"能下推的函数现在不会了。如果你发现查询变慢,检查一下是否撞上了这个变化。
升级检查清单
- 必须执行:
ALTER EXTENSION pg_clickhouse UPDATE TO '0.3';(修复 SSRF) - 检查依赖:如果用了自动下推的内置函数,确认它们是否还在显式映射列表中
- 二进制驱动用户:JSON 类型映射现在可用,考虑把
text列改为jsonb以获得更好的查询能力 - 正则用户:安装
re2扩展,统一 Postgres 和 ClickHouse 的正则行为 - 多字节文本用户:
length()和strpos()下推目标已改为 UTF-8 版本,验证结果是否符合预期
pg_clickhouse 的方向很清晰——能推下去的都推下去,推不了的就补映射、修语义差异。0.3.0 在 JSON、正则、格式化这几个高频场景上补了关键短板,SSRF 修复则让生产部署更安心。如果你在用 Postgres 做"统一入口"而把分析数据留在 ClickHouse,这个版本值得立刻升级。