PostgreSQL 正则表达式的新选择:用 pg_tre 和 pg_re2 做对比测试

2026-08-26 36 预计阅读时间: 1 分钟
来源: postgr.es AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:8 分钟

PostgreSQL 自带强大的正则表达式能力,但在不同工作负载下,正则引擎的语法、执行特性和安全边界可能影响查询结果与响应时间。Hubert “depesz” Lubaczewski 最近整理了 pg_trepg_re2,并使用 explain.depesz.com 数据库中的执行计划数据进行研究。

这类尝试的价值不只是“换一个正则函数”。更重要的是:把真实数据准备好,确认扩展暴露的 API,再用同一组查询比较匹配结果和执行代价。

先准备可重复的测试数据

文章中的测试数据来自 explain.depesz.com 数据库。作者把执行计划提取到一张名为 all_plans 的侧表中,后续可以在相同数据集上反复测试不同正则实现。

如果你也想构建一个最小测试环境,可以先准备一张包含文本字段的表:

CREATE TABLE all_plans (
    id         bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    plan_text  text NOT NULL,
    captured_at timestamptz NOT NULL DEFAULT now()
);

INSERT INTO all_plans (plan_text) VALUES
    ('Index Scan using orders_pkey on orders  (cost=0.42..8.44 rows=1 width=8)'),
    ('Seq Scan on events  (cost=0.00..431.00 rows=12000 width=64)'),
    ('Nested Loop  (cost=1.12..42.80 rows=5 width=16)');

真实数据通常比人工构造的三行样本更有意义,因为它包含不同长度、不同字符集和不同匹配密度的文本。测试时应固定数据快照,避免因为数据变化而误判某个正则引擎更快。

pg_trepg_re2 该怎么看

从名字可以看出,两个扩展分别尝试把 TRE 和 RE2 正则实现带入 PostgreSQL。实践中需要关注三个维度:

  • 语法兼容性:现有 PostgreSQL 正则表达式是否可以直接迁移,还是需要调整转义和分组写法。
  • 匹配语义:捕获组、重复匹配、大小写处理以及非法表达式的报错方式是否与现有查询一致。
  • 执行边界:复杂正则在大文本列上可能消耗大量 CPU,某些引擎对最坏情况的控制方式也不同。

不要仅凭扩展名称判断它一定适合生产环境。应以扩展版本、构建选项和实际 API 为准,并分别验证结果与性能。

安装后可以用 PostgreSQL 自带的元命令确认扩展和函数:

psql "$DATABASE_URL" -c "CREATE EXTENSION IF NOT EXISTS pg_tre;"
psql "$DATABASE_URL" -c "CREATE EXTENSION IF NOT EXISTS pg_re2;"
psql "$DATABASE_URL" -c "\\dx"
psql "$DATABASE_URL" -c "\\df *tre*"
psql "$DATABASE_URL" -c "\\df *re2*"

这里的函数搜索很重要:不同版本或打包方式可能提供不同函数名、操作符或参数形式。先查看数据库实际安装的签名,再把业务查询接入,能避免照抄不匹配的示例。

用同一批查询比较结果和代价

可以先使用 PostgreSQL 内置正则作为基线。下面的查询找出包含 Index ScanSeq Scan 的执行计划,并统计匹配数量:

SELECT
    count(*) FILTER (WHERE plan_text ~ 'Index Scan') AS index_scan_count,
    count(*) FILTER (WHERE plan_text ~ 'Seq Scan')   AS seq_scan_count
FROM all_plans;

如果扩展提供与操作符兼容的接口,可以把正则表达式和查询主体保持不变,只替换匹配调用。若扩展使用函数,则可以按实际签名改写成类似下面的形式:

-- 假设扩展提供 boolean 返回值的匹配函数。
-- 运行前请通过 \\df 确认真实函数名和参数顺序。
SELECT count(*)
FROM all_plans
WHERE re2_match(plan_text, 'Index Scan');

这段代码中的 re2_match 是接口形态示例,不应在未确认扩展 API 的情况下直接部署。可复制的验证流程是:

EXPLAIN (ANALYZE, BUFFERS, TIMING)
SELECT count(*)
FROM all_plans
WHERE plan_text ~ '(Index|Seq) Scan';

pg_trepg_re2 分别执行等价查询,并记录以下信息:匹配行数、执行时间、扫描行数、CPU 使用情况,以及是否出现异常增长的耗时。只看总执行时间不够,因为小数据集可能掩盖长文本或高并发场景的问题。

一个实用的测试矩阵

为了让结果具有解释力,可以准备几类正则表达式:

CREATE TEMP TABLE regex_cases (
    case_name text PRIMARY KEY,
    pattern   text NOT NULL
);

INSERT INTO regex_cases (case_name, pattern) VALUES
    ('literal',      'Index Scan'),
    ('alternation',  '(Index|Seq) Scan'),
    ('group',        'cost=([0-9.]+)'),
    ('line_marker',  '^Seq Scan');

SELECT c.case_name,
       c.pattern,
       count(p.id) AS matched_rows
FROM regex_cases AS c
JOIN all_plans AS p
  ON p.plan_text ~ c.pattern
GROUP BY c.case_name, c.pattern
ORDER BY c.case_name;

在扩展测试中,保持 regex_casesall_plans 不变,只替换正则执行方式。这样可以分别观察简单字面量、分支、捕获组和锚点表达式的行为。

还要测试异常输入和边界输入,例如空字符串、超长文本、无效 UTF-8 数据来源,以及可能导致极高计算量的模式。正则匹配发生在数据库进程中,查询超时、资源组和并发限制都应纳入设计。

采用前的检查清单

pg_trepg_re2 更适合通过实验进入系统,而不是直接替换现有正则逻辑。建议完成以下检查:

  • 固定一份生产风格的数据快照。
  • \\dx\\df 确认扩展版本、函数名和返回类型。
  • 对每条关键查询同时比较匹配结果和 EXPLAIN (ANALYZE, BUFFERS) 输出。
  • 检查现有正则语法在新引擎中是否保持相同语义。
  • 为超时、CPU 消耗和异常模式设置保护措施。
  • 在升级 PostgreSQL 或扩展版本后重新跑回归测试。

真正值得迁移的标准不是“某个引擎在一次测试中更快”,而是它能在你的数据分布、正则语法和并发模型下稳定地提供相同结果,并且具备可接受的资源边界。


相关推荐