使用 python 和 javascript 的慢正则表达式,但在 go 和 php 中快速失败

Posted

技术标签:

【中文标题】使用 python 和 javascript 的慢正则表达式,但在 go 和 php 中快速失败【英文标题】:Slow regex with python and javascript but fast to fail in go and php 【发布时间】:2019-01-23 14:03:03 【问题描述】:

我编写了一个正则表达式来解析 PostgreSQL 错误,以尝试向用户显示哪个字段有重复数据。 正则表达式是这样的:

^DETAIL:.[^\(]+.(.[^\)]+).[^\(]+.(.[^\)]+). already exists

如果您针对正确的消息(https://regex101.com/r/GZuREV/1)运行它会非常快:

ERROR:  duplicate key value violates unique constraint "uq_content_block_internal_name_store_id"
DETAIL:  Key (lower(internal_name::text), store_id)=(some content block-32067683, 0c6d20a7-d843-44f3-af9c-4a2cf2a47e4c) already exists.

但如果 PostgreSQL 发出另一条类似以下的消息,python 将需要大约 30 秒才能在我的机器中回复 (https://regex101.com/r/GZuREV/2)。

ERROR:  null value in column "active" violates not-null constraint
DETAIL:  Failing row contains (2018-08-16 14:23:52.214591+00, 2018-08-16 14:23:52.214591+00, null, 6f6d1bc9-c47e-46f8-b220-dae49bd58090, bf24d26e-4871-4335-9f18-83c5a52f1b3a, Some Product-a1c03dde-2de9-401c-92d5-5c1500908984, "de_DE": "Fugit tempore voluptas quos est vitae.", "en_GB": "Qu..., "de_DE": "Fuga reprehenderit nobis reprehenderit natus magni es..., "de_DE": "Fuga provident dolorum. Corrupti sunt in tempore quae..., my-product-53077578, SKU-53075778, 600, 4300dc25-04e2-4193-94c0-8ee97b636739, 52553d24-6d1c-4ce6-89f9-4ad765599040, null, 38089c3c-423f-430c-b211-ab7a57dbcc13, 7d7dc30e-b06b-48b7-b674-26d4f705583b, null, , 0, null, 9980, 100, 1, 5).

如果转到 regex101 链接,您可以看到,如果您切换到不同的语言,如 php 或 go,它们都会很快返回,说没有找到匹配项,但如果您选择 python 或 javascript,您将获得超时。

我的快速脏修复是这样的:

match = 'already exists' in error_message and compiled_regex.search(error_message)

您认为这可能是什么原因造成的?在我达到我想要的数据之前,会不会是贪婪的运营商消费?

更新 1

在 python 中使用 re.IGNORECASE 会使其慢约 9 秒,因为它花费了太多时间来小写一些东西。

忽略大小写

没有忽略大小写

更新 2

玩弄我发现要让它工作并失败,一个简单的改变就会失败

^DETAIL:.[^\(]+?\((.[^\)]+?).[^\(]+?.(.[^\)]+?). already exists
                            ^ just changing this to \) make it stop timing out
^DETAIL:.[^\(]+?\((.[^\)]+?)\)[^\(]+?.(.[^\)]+?). already exists

【问题讨论】:

如果我删除初始 ^ 或使用多行,它看起来会变得非常慢。 【参考方案1】:

这是一个正则表达式怪物:)

为什么不拆分 2 个正则表达式?

    检查already exists 是否匹配(非常快) 使用现有的正则表达式^DET.[^\(]+.(.[^\)]+).[^\(]+.(.[^\)]+)提取要显示的数据

这应该会大大加快您的代码速度。 (你甚至可以像我一样缩短 DETAIL)

【讨论】:

【参考方案2】:

Package regexp

import "regexp"

包正则表达式实现正则表达式搜索。

接受的正则表达式的语法是一样的通用 Perl、Python 和其他语言使用的语法。更准确地说,它 是 RE2 接受的语法,描述于 https://golang.org/s/re2syntax,除了 \C。概览 语法,运行

go doc regexp/syntax

此包提供的正则表达式实现保证 与输入的大小成线性时间运行。 (这是一个属性不是 由常规的大多数开源实现保证 表达式。)有关此属性的更多信息,请参阅

http://swtch.com/~rsc/regexp/regexp1.html

或任何有关自动机理论的书。


按照设计,Go 正则表达式保证在输入大小上线性运行,这是一些其他正则表达式实现无法保证的属性。见Regular Expression Matching Can Be Simple And Fast。

【讨论】:

【参考方案3】:

这并不是问题的真正答案,但我认为问题可能是贪婪的运营商。无论如何,我认为你应该让其中的一部分变得懒惰以快速失败。

我使用了这种模式,并且在 regex101 上的所有语言引擎上都可以:

^DETAIL:.+?\((.+)\).+?\((.+)\) already exists.

link to the matched error

link to the error with no match

【讨论】:

那个不错,我忘了+?可以让它变得懒惰,谢谢!【参考方案4】:

TL;DR:使用这个:

^DETAIL:\s*+Key[^\(]++\((.+)\)[^\(]+\(([^\)]+)\) already exists

见matching example和non-matching one

解释:

首先,原始正则表达式似乎与整个键组不匹配,您停在lower(internal_name::text,省略了组合键的一些列以及不平衡的括号。如果您像这样修改它,它应该可以捕获复合键。如果不应该这样做,请告诉我:

^DETAIL:.[^\(]+.(.+)\)[^\(]+.(.[^\)]+). already exists

仅仅通过改变这个,正则表达式是“可运行的”,但仍然很慢。

他的主要原因之一是这个[^\(]+。它首先匹配到DETAIL: Failing row contains(space),然后与正则表达式的其余部分匹配。它不会匹配,所以它会回溯到少一个字符,直到 DETAIL: Failing row contains 并继续使用正则表达式的其余部分。它不会匹配,所以会回到DETAIL: Failing row contain...等。

避免这种情况的一种方法是使用所有格量词。这意味着一旦你拿了东西,你就不能回去了。所以使用这个[^\(]++ 而不是这个[^\(]+(即:^DETAIL:.[^\(]++.(.+)\)[^\(]+.(.[^\)]+). already exists)会使正则表达式的步数从28590减少到1290。

但你仍然可以改进它。如果您知道您需要的数据使用关键字key,请使用它!这样,由于失败的示例中不存在它,它将使正则表达式很快失败(一旦它读取 DETAIL 和下一个单词)

所以如果你使用^DETAIL:\s*+Key[^\(]++.(.+)\)[^\(]+.(.[^\)]+). already exists,现在只有 12 步。

如果您觉得使用 key 过于具体,您可以使用不那么通用的东西来尝试查找“不是‘失败’”。像这样:

^DETAIL:\s*+(?!Fail)[^\(]++.(.+)\)[^\(]+.(.[^\)]+). already exists

这样是17步。

最后,您可以调整匹配内容的正则表达式。

改变这个:

^DETAIL:\s*+Key[^\(]++.(.+)\)[^\(]+
.           # <============= here, use \( instead
(.[^\)]+). already exists

通过这个:

^DETAIL:\s*+Key[^\(]++.(.+)\)[^\(]+\((.[^\)]+). already exists

这将步骤从 538 减少到 215,因为您减少了回溯。

然后,在删除几个无用的点并用\(\)(个人喜好)替换一些(假定为括号)点后,您就有了最终的正则表达式:

^DETAIL:\s*+Key[^\(]++\((.+)\)[^\(]+\(([^\)]+)\) already exists

【讨论】:

以上是关于使用 python 和 javascript 的慢正则表达式,但在 go 和 php 中快速失败的主要内容,如果未能解决你的问题,请参考以下文章

python 配置文件Flask的慢响应请求

使用 selenium 和 beautifulsoup 的慢代码

连接表的慢查询

SequelizeJS 中的慢关联

存储优化-排序引起的慢查询优化

AMD 上的慢模板纹理