
本文字数:4947;估计阅读时间:13 分钟
作者:David Wheeler and Philip Dubé

编者按: 本文译自 ClickHouse 原博客。 原文围绕「基于RE2的Postgres高速正则表达式扩展」展开。对于Postgres用户而言,pg_re2扩展的引入为正则表达式操作带来了显著的性能提升。其基于RE2引擎,有望解决传统正则表达式的性能瓶颈。
速度是 ClickHouse 产品设计与工程的核心驱动力。我们对性能的重视,在开发 ClickBench 和 PostgresBench 等基准测试中可见一斑,其中一些已成为行业标准。这种理念自然也贯穿于我们产品的方方面面,甚至延伸至 SQL 函数和运算符。
秉持这一理念,我们推出了 pg_re2,这是一个 Postgres 扩展,旨在为 Postgres 提供基于 RE2 的高速正则表达式功能。你可以通过 PGXN 或 GitHub 安装它;ClickHouse Managed Postgres 已预装此扩展。
安装方式如下:
CREATE EXTENSION re2;
安装后即可开始使用。
SELECT 'HouseQuake' @~ 'Quake\b';
?column?
----------
t
(1 row)
有关完整功能列表,请参阅文档(https://pgxn.org/dist/re2/doc/re2.html)。
Postgres 本身已提供丰富的正则表达式运算符和函数。既然如此,为何还要提供替代方案?主要原因有二:速度与兼容性。
速度需求
究其根本,Postgres 和 RE2 的正则表达式引擎基于截然不同的搜索算法。Postgres 的 POSIX 正则表达式采用回溯(backtracking)算法,返回最长匹配结果;而 RE2 则依赖于有限自动机(finite automata)。2010 年,Russ Cox 在其宣布 RE2 的文章中总结称:
RE2 证明,利用自动机理论几乎可以实现像 PCRE 这样的现代回溯正则表达式库的所有功能。由于其深植于自动机理论基础,RE2 在执行时间上提供了比临时实现更强的保障,并支持那些临时实现难以或无法完成的高级分析。最后,RE2 是开源的。祝您使用愉快!
鉴于这些发现,ClickHouse 的字符串搜索、字符串替换、字符串提取 和字符串分割功能均以 RE2 为基础。我们也希望为 Postgres 用户带来同样优势。
基准测试
pg_re2 项目 中包含一个基准测试,旨在比较其基于 RE2 的正则表达式函数与 Postgres 原生函数的实际性能。下图总结了在运行 PostgreSQL 18 的 AWS m6g.2xlarge 实例上的横向加速倍数:

测试结果如下:
• RE2 在所有测试中均表现出色 ,性能达到 Postgres 的 1.8 倍至 8.6 倍。
• 最大的性能提升来自 extract_all (7.2-8.6 倍)。该函数直接返回数组,与 Postgres regexp_matches(..., 'g') 函数因返回集合而产生的 坦承不公平的 额外开销相比,优势显著。
索引
pg_re2 v0.4.0 新增了匹配运算符 @~,作为 Postgres ~ 运算符的补充。此外,它还增加了 btree 和 GIN 索引支持,以减少评估 RE2 正则表达式时所需的表扫描。第二个基准测试比较了在同一服务器上 RE2 与 Postgres 正则表达式索引的性能表现:

研究发现如下:
• RE2 在所有测试中再次领先 ,相比等效的 Postgres 操作,性能提升了 1.1 到 1.8 倍。
• 对 btree 索引进行 前缀 匹配时,其整体加速效果与全表扫描相似,因为两者都能以原始执行速度处理索引条目。
• gin_re2_ops GIN 操作类受益于收集 RE2 所需的字节等效 三元组 ,甚至包括跨越标点符号的情况。因此,像 error_code=420-9 这类模式可以使用索引,而当 Postgres 的正则表达式使用 pg_trgm 操作类时,则会回退到全表扫描。
• 对于 普通词模式 ,RE2 的 gin_re2_ops 和 Postgres 的 pg_trgm 操作类提取的键相似,且 pg_trgm 较低开销的一致性检查可能更具优势(参见上文的 error_code=123 )。
总体而言,Russ Cox 提出的性能改进在 Postgres 的正则表达式中同样适用。
高度兼容
创建 pg_re2 的第二个原因是为了与 ClickHouse 正则表达式函数兼容。事实证明,RE2 不仅速度有别,其语法也与 Postgres POSIX 表达式存在差异。这些差异至关重要,因为 pg_clickhouse v0.2.0 为多个 PostgreSQL 运算符和函数添加了下推(pushdown)支持。这对于简单的正则表达式效果良好:
SELECT id, by FROM log.entries WHERE by ~ 'Click' ORDER BY id;
id | by
----+------------
1 | ClickHouse
5 | MouseClick
(2 rows)
如果你想尝试这些示例,下面的例子使用了这个 ClickHouse 表:
CREATE TABLE entries ( id UInt32 NOT NULL, by String NOT NULL ) ENGINE = MergeTree ORDER BY (id); INSERT INTO entries VALUES ( 1, 'ClickHouse' ), ( 2, 'HouseQuake' ), ( 3, 'HouseQuake' ), ( 4, 'QuakerOats' ), ( 5, 'MouseClick' );以及 Postgres 中的这个 pg\_clickhouse 外部表配置:
CREATE EXTENSION pg_clickhouse; CREATE EXTENSION re2; CREATE SERVER ch FOREIGN DATA WRAPPER clickhouse_fdw OPTIONS(driver 'binary'); CREATE USER MAPPING FOR CURRENT_USER SERVER ch; CREATE SCHEMA log; IMPORT FOREIGN SCHEMA "default" FROM SERVER ch INTO log;
它也适用于简单的锚定表达式,例如 `^` 和 `$`:
SELECT id, by FROM log.entries WHERE by ~ '^House' ORDER BY id;
id | by
----+------------
2 | HouseQuake
3 | HouseQuake
(2 rows)
但有时 pg\_clickhouse 会返回意想不到的结果:
SELECT id, by ~ 'Quake\b' FROM log.entries WHERE by ~ 'Quake\b';
id | ?column?
----+----------
2 | f
3 | f
(2 rows)
在此例中,`by ~ 'Quake\b'` 在 `WHERE` 子句中会被下推到 ClickHouse 执行,并对两行数据返回 true;但在 SELECT 列表中执行的相同表达式(它在 Postgres 中执行),结果却出人意料地返回 false。这究竟是为何?
Postgres POSIX 与 ClickHouse RE2
这个令人费解的结果源于两个引擎之间的差异。在 ClickHouse 中,\b 代表词边界,而在 Postgres 中它代表退格字符。因此,下推到 ClickHouse 的 `WHERE` 表达式返回真,但在 Postgres 中执行的 SELECT 表达式则不返回真。
这两个引擎之间存在诸多此类差异。许多反斜杠(\)特性在两者中的工作方式不同,或仅在其中一个引擎中存在:

两者都支持通过 (?xyz) 或作为 Postgres 函数的额外参数来传递标志。然而,这些标志的差异也十分显著:

两个引擎都支持的标志,其表述虽有不同,但*似乎*会产生等效行为。例如,PostgreSQL 的术语"非换行符匹配"意味着模式对换行符不敏感,因此*确实*匹配换行符。这些文档差异使得在没有实际测试的情况下,难以验证其行为。
此外,还有一些例子:
-
ClickHouse 不支持任意的先行断言或后行断言 ((?=re), (?!re), (?<=re), and (?<!re))
-
ClickHouse 不支持反向引用 (\1, \2, etc.),例如用于匹配引号的 ("')(.*?)\1;但在替换表达式中,两者都支持反向引用。
-
排序规则和区域设置决定了 Postgres 字符类中包含哪些非 ASCII 字符,而 ClickHouse RE2 字符类只包含 ASCII 字符。
影响
这些差异只是两个正则表达式引擎之间诸多区别中的一小部分。显然,在查询 pg\_clickhouse 表时,必须警惕这些差异。务必彻底测试你的正则表达式,尤其是在使用标志、字符类和反斜杠特性时。
为了简化问题(或者可能制造 两个问题(https://regex.info/blog/2006-09-15/247 "Source of the famous "Now you have two problems" quote")),我们在 pg\_clickhouse v0.3.0 中增加了 pg\_re2 函数的下推功能。与下推 Postgres 正则表达式函数相比,在 pg\_clickhouse 表的列中使用 pg\_re2 函数具有多项优势:
-
无需判断是否发生下推,因为两个数据库上的行为一致。
-
RE2 提供渐近更优的性能,这一点我们已在 前文 提及。
-
对于 ClickHouse 用例,只需处理一种正则表达式语法。
当我们回顾本节开头提到的那些意想不到的结果时,这些优势便会显而易见:
SELECT id, by ~ 'Quake\b' FROM log.entries WHERE by ~ 'Quake\b';
id | ?column?
----+----------
2 | f
3 | f
(2 rows)
再次强调,~ 运算符下推到 ClickHouse 时返回 true,但在 Postgres 中执行时返回 false。将 Postgres 的 ~ 运算符替换为 re2 扩展的 re2match() 函数后,它仍会下推至 ClickHouse 的 match function:
SELECT id, re2match(by, 'Quake\b')
FROM log.entries where re2match(by, 'Quake\b');
id | re2match
----+----------
2 | t
3 | t
(2 rows)
但由于 re2 扩展与 ClickHouse 使用相同的 RE2 库,查询现在能够返回一致的结果:re2match() 在 Postgres 中执行时返回 true,下推到 ClickHouse 执行时也返回 true。
开始使用
re2 扩展 的开发宗旨,是将 Postgres 与 ClickHouse 整合为一个协同工作的高性能数据平台。为此,它复刻了 ClickHouse 完整的正则表达式函数集,这些函数在 字符串搜索、字符串替换、字符串提取 和 字符串分割 文档中均有描述。此外,其测试套件也借鉴了 ClickHouse 现有的正则表达式测试。
因此,我们建议在条件允许时,尽可能使用 pg_re2 来提升 Postgres 上正则表达式的执行速度。对于分析查询尤其如此,这有助于确保数据迁移到 ClickHouse 后,性能和下推(pushdown)保持一致。
关于我们
ClickHouse 是面向 AI 时代打造的高性能实时分析数据库,能够以极致性能处理海量数据分析任务。凭借高并发、低延迟和云原生架构,ClickHouse 广泛应用于可观测性、数据仓库、实时分析及 AI 数据基础设施等场景。我们致力于帮助企业在公有云平台上构建安全、弹性且高性价比的实时分析与 AI 数据平台,加速释放数据价值,推动智能化创新与数字化转型。目前,Trip.com、DiDi、Meta、Sony、Netflix、Deutsche Bank、Sierra、Cloudflare 等全球领先企业均在使用 ClickHouse 支撑其关键业务和数据分析平台。