[英]Slow regex with python and javascript but fast to fail in go and php
我写了一个正则表达式来解析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发出如下所示的另一条消息,我的机器中需要大约30秒的python才能回答( 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秒。
用ignorecase
没有忽视
更新2
在我周围玩耍时,我发现它可以使它工作并且失败,一个简单的改变就会破裂
^DETAIL:.[^\(]+?\((.[^\)]+?).[^\(]+?.(.[^\)]+?). already exists
^ just changing this to \) make it stop timing out
^DETAIL:.[^\(]+?\((.[^\)]+?)\)[^\(]+?.(.[^\)]+?). already exists
import "regexp"
包regexp实现正则表达式搜索。
接受的正则表达式的语法与Perl,Python和其他语言使用的通用语法相同。 更确切地说,它是RE2接受的语法,并在https://golang.org/s/re2syntax中描述,除了\\ C。 有关语法的概述,请运行
go doc regexp/syntax
此包提供的regexp实现保证在输入大小的时间线性运行。 (这是正则表达式的大多数开源实现无法保证的属性。)有关此属性的更多信息,请参阅
http://swtch.com/~rsc/regexp/regexp1.html
或任何关于自动机理论的书。
通过设计,Go正则表达式可以保证在输入的大小上按时间线性运行,这是正则表达式的其他一些实现无法保证的属性。 请参阅正则表达式匹配可以简单快速 。
TL; DR:使用此:
^DETAIL:\\s*+Key[^\\(]++\\((.+)\\)[^\\(]+\\(([^\\)]+)\\) already exists
解释:
首先,原始的regexp似乎与整个键组不匹配,你停在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
^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
这是一个正则表达式的怪物:)
为什么不拆分2个正则表达式?
already exists
匹配(非常快) ^DET.[^\\(]+.(.[^\\)]+).[^\\(]+.(.[^\\)]+)
这应该会加速你的代码。 (你甚至可以像我一样缩短细节)
这不是问题的答案,但我认为问题可能是贪婪的运营商。 无论如何,我认为你应该让它的某些部分懒得快速失败。
我使用了这种模式,它在regex101上的所有语言引擎上都可以:
^DETAIL:.+?\((.+)\).+?\((.+)\) already exists.
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.