MariaDB 升级到 PCRE-8.34

今天,我们将 MariaDB-10.0 中捆绑的 PCRE 库升级到了 PCRE-8.34。此 PCRE 版本包含一些改进、用于提高稳定性和性能的修复,并提供了与 Perl 正则表达式更好的兼容性。

我想详细介绍一下对 MariaDB 影响特别大的 PCRE 变化。

PCRE 现在包含对 [[:<:]] 和 [[:>:]]  的支持,这与 BSD POSIX 库(由 Henry Spencer 编写)中使用的含义相同,分别表示“单词开头”和“单词结尾”。对于从 Henry Spencer 的库迁移到 PCRE 的项目(如 MariaDB)来说,这是个好消息,因为这种非标准语法似乎被广泛使用。非常感谢 Philip Hazel 和 PCRE 团队,他们慷慨地将此扩展添加到 PCRE 中,并在最终发布 8.34 之前提供了补丁,因此我们能够更早地修复与 MySQL RLIKE 的不兼容问题(参见“MDEV-5357 REGEXP 单词边界不起作用”,已在 Maria-10.0.7 中修复)。

PCRE-8.33 还修复了在模式包含非常深的嵌套括号时,pcre_compile() 中栈溢出导致的崩溃。PCRE 现在对括号的嵌套深度有一个编译时限制(默认为 250)。这对于使用操作系统默认栈大小设置的程序来说工作良好,并且 pcre_compile() 现在可以安全地返回错误,而不是崩溃。然而,不幸的是,这个新限制对我们没有帮助,因为 MariaDB 使用较小的单个线程栈大小,这需要处理数万个并发连接,并且由默认值为 288Kb 的 @@thread_stack MariaDB 系统变量控制(而默认的 Posix 线程栈大小为 8Mb)。在默认的 @@thread_stack=288Kb 下,MariaDB 仍然会使用 PCRE-8.34 的逐字复制版本在如下查询中崩溃:

SELECT 'a' RLIKE REPEAT('(', 1000);

在不同的操作系统上,导致崩溃的确切数字可能会有所不同。在我对 Fedora 机器进行的测试中,这个数字约为 210,比新的 PCRE 默认限制稍小。

为了防止崩溃,我们不得不保留我们的补丁,该补丁在 PCRE 中添加了一个回调函数。有关详细信息,请参阅 pcre/mariadb-patches/pcre_stack_guard.diff。每当正则表达式模式中遇到括号时,pcre_compile() 都会在进入下一个递归级别之前调用此回调函数。如果线程栈大小变得危险地小,mysqld 会通过回调函数的返回值指示这一点,pcre_compile() 将返回错误,并且整个 SQL 查询会显示错误消息:

mysql> SELECT 'a' RLIKE REPEAT('(',1000);
ERROR 1436 (HY000): Thread stack overrun:
263672 bytes used of a 294912 byte stack, and 32000 bytes needed.
Use 'mysqld --thread_stack=#' to specify a bigger stack

如果通过使用例如 --thread-stack=589824 启动 mysqld 来提供更大的栈大小,线程就不会过早地耗尽栈,而是会触及编译后的 PCRE 限制,这会由不同的错误消息指示:

mysql> SELECT 'a' RLIKE REPEAT('(',1000);
ERROR 1139 (42000): Got error 'parentheses are too deeply nested at offset 251' from regexp

我们将关注 PCRE 未来版本是否会添加内置的方式来控制使用的栈大小。在此期间,即使系统已安装 PCRE,我们也必须继续编译捆绑的已打补丁版本。

请参阅 http://www.pcre.org/changelog.txt 以获取 PCRE-8.34 的完整更改列表。我们感谢 PCRE 团队的优秀发布。