【问题标题】:How to improve performance for REGEXP string matching in MySQL?如何提高 MySQL 中 REGEXP 字符串匹配的性能?
【发布时间】:2016-06-20 19:34:58
【问题描述】:

前言:

我对此进行了大量(重新)搜索,并找到了以下 SO 帖子/答案:https://stackoverflow.com/a/5361490/6095216,这与我正在寻找的内容非常接近。相同的代码,但更有用的 cmets 出现在此处:http://thenoyes.com/littlenoise/?p=136

问题描述:

我需要将 1 列 MySQL TEXT 数据拆分为多列,其中原始数据具有这种格式(N

{"field1":"value1","field2":"value2",...,"fieldN":"valueN"}

正如您可能猜到的,我只需要提取,将每个值放入单独的(预定义)列中。问题是字段的数量和顺序不能保证对于所有记录都是相同的。因此,使用 SUBSTR/LOCATE 等的解决方案不起作用,我需要使用正则表达式。另一个限制是不能使用 3rd 方库,例如 LIB_MYSQLUDF_PREG(在我上面第一个链接的答案中建议)。

目前的解决方案/进展:

我已经修改了上述链接中的代码,使其从左到右返回第一个/最短匹配;否则,返回 NULL。我还对其进行了一些重构,并使标识符对阅读器/维护者更友好:) 这是我的版本:

CREATE FUNCTION REGEXP_EXTRACT_SHORTEST(string TEXT, exp TEXT)
RETURNS TEXT DETERMINISTIC
BEGIN
    DECLARE adjustStart, adjustEnd BOOLEAN DEFAULT TRUE;
    DECLARE startInd INT DEFAULT 1;
    DECLARE endInd, strLen INT;
    DECLARE candidate TEXT;

    IF string NOT REGEXP exp THEN
        RETURN NULL;
    END IF;

    IF LEFT(exp, 1) = '^' THEN
        SET adjustStart = FALSE;
    ELSE
        SET exp = CONCAT('^', exp);
    END IF;
    IF RIGHT(exp, 1) = '$' THEN
        SET adjustEnd = FALSE;
    ELSE
        SET exp = CONCAT(exp, '$');
    END IF;

    SET strLen = LENGTH(string);
    StartIndLoop: WHILE (startInd <= strLen) DO
        IF adjustEnd THEN
            SET endInd = startInd;
        ELSE
            SET endInd = strLen;
        END IF;
        EndIndLoop: WHILE (endInd <= strLen) DO
            SET candidate = SUBSTRING(string FROM startInd FOR (endInd - startInd + 1));
            IF candidate REGEXP exp THEN
                RETURN candidate;
            END IF;
            IF adjustEnd THEN
                SET endInd = endInd + 1;
            ELSE
                LEAVE EndIndLoop;
            END IF;
        END WHILE EndIndLoop;
        IF adjustStart THEN
            SET startInd = startInd + 1;
        ELSE
            LEAVE StartIndLoop;
        END IF;
    END WHILE StartIndLoop;
    RETURN NULL;
END;

然后我添加了一个辅助函数以避免重复正则表达式模式,正如您从上面看到的那样,所有字段都是相同的。这是那个函数(我留下了我尝试使用lookbehind - 在MySQL中不受支持 - 作为评论):

CREATE FUNCTION GET_MY_FLD_VAL(inputStr TEXT, fldName TEXT)
RETURNS TEXT DETERMINISTIC
BEGIN
    DECLARE valPattern TEXT DEFAULT '"[^"]+"'; /* MySQL doesn't support lookaround :( '(?<=^.{1})"[^"]+"'*/
    DECLARE fldNamePat TEXT DEFAULT CONCAT('"', fldName, '":');
    DECLARE discardLen INT UNSIGNED DEFAULT LENGTH(fldNamePat) + 2;
    DECLARE matchResult TEXT DEFAULT REGEXP_EXTRACT_SHORTEST(inputStr, CONCAT(fldNamePat, valPattern));
    RETURN SUBSTRING(matchResult FROM discardLen FOR LENGTH(matchResult) - discardLen);
END;

目前,我要做的只是使用上述代码进行简单的 SELECT 查询。它工作正常,但它。是。 SLOOOOOOOW ...最多只能拆分 7 个字段/列(并非所有记录都有 7 个)!限制为 20 条记录,大约需要 3 分钟 - 我总共有大约 40,000 条记录(对于数据库来说不是很多,对吧?!):)

所以,最后,我们得到了实际的问题:[如何] 上述算法/代码(此时几乎是粗略搜索)在性能方面得到显着改进,以便它可以在实际运行在合理的时间内建立数据库?我开始研究主要的已知模式匹配算法,但很快就迷失了,试图找出什么是合适的,这在很大程度上是由于可用选项的数量及其各自的限制、使用条件等。另外,它似乎在 SQL 中实现其中一个只是为了看看它是否有帮助,可能需要做很多工作。

注意:这是我的第一篇文章(!),所以如果有什么不清楚的地方,请告诉我(很好地),我会尽力解决它。提前致谢。

【问题讨论】:

  • 你正在打一场你在这里无法获胜的战斗。正则表达式总是很慢,它们不能被索引。如果这是一个 JSON 列,并且您还不太喜欢 MySQL,那么 Postgres 对 JSON 和对内容的深度索引的 fantastic 支持。
  • 或升级到 5.7.8 或更高版本以获得 MySQL 中的原生 JSON 支持 dev.mysql.com/doc/refman/5.7/en/json.html
  • @tadman 是的,但即使使用正则表达式,我也很确定我的解决方案可以改进。例如,我使用了来自loggly.com/blog/… 的建议并将 * 量词更改为 +,这使我的查询运行速度快了近 3 倍(忘了在我的问题中提及)。我的下一个想法是尝试使用lookbehind,这样它就不必每次都从字符串的开头开始查找(是的,我知道我说过不能保证字段顺序......) - 但 MySQL 没有不支持。
  • @tadman 如果建议 Postgres(作为拆分数据的临时占位符,然后将其放回 MySQL),我会给你 +1,但我认为我的声誉还不够高.虽然这似乎是一种非常激进的方法:)
  • 我对 Postgres + JSON 有过一些非常好的体验,尤其是新优化的 JSONB 列,它提供了改进的性能和更紧凑的存储。此处的索引和查询功能可与 MongoDB 等文档存储相媲美。

标签: mysql regex algorithm sqlperformance


【解决方案1】:

正如上面 tadman 和 Matt Raines 所建议的那样,我能够通过解析 JSON 来解决这个问题。作为 JSON 概念的新手,我根本没有意识到它可以以这种方式完成......有点尴尬,但吸取了教训!

不管怎样,我在common_schema框架中使用了get_option函数:https://code.google.com/archive/p/common-schema/(通过这篇文章找到,也演示了如何使用该函数:Parse JSON in MySQL)。结果,我的 INSERT 查询需要大约 15 分钟才能运行,而使用 REGEXP 解决方案则需要 30 多个小时。谢谢,直到下一次! :)

【讨论】:

    【解决方案2】:

    不要在 SQL 中这样做;使用 PHP 或其他具有解析 JSON 的内置工具的语言来完成。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-01-09
      • 1970-01-01
      • 1970-01-01
      • 2021-10-04
      • 1970-01-01
      • 2013-05-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多