如何提高 MySQL 中 REGEXP 字符串匹配的性能?

How to improve performance for REGEXP string matching in MySQL?

前言:

我对此做了很多(重新)搜索,并找到了以下 SO post/answer: which was pretty close to what I'm looking for. The same code, but with somewhat more helpful comments, appears here: http://thenoyes.com/littlenoise/?p=136 .

问题描述:

我需要将 MySQL TEXT 数据的 1 列拆分为多列,其中原始数据的格式为 (N <= 7):

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

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

Solution/Progress 到目前为止:

我修改了上面 link 的代码,使其 returns 和 first/shortest 从左到右匹配;否则,返回 NULL。我还对其进行了一些重构,使标识符更多 reader/maintainer-friendly :) 这是我的版本:

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;

然后我添加了一个辅助函数以避免重复正则表达式模式,正如您从上面看到的那样,所有字段都相同。这是该功能(我尝试使用后视 - 在 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 个 fields/columns 可以拆分,最大值(并非所有记录都有全部 7 个)!限制为 20 条记录,大约需要 3 分钟 - 我总共有大约 40,000 条记录(对于数据库来说不是很多,对吧?!):)

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

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

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

总之,我使用了common_schema框架中的get_option函数:https://code.google.com/archive/p/common-schema/ (found through this post, which also demonstrates how to use the function: Parse JSON in MySQL)。结果,我的 INSERT 查询花费了大约 15 分钟到 运行,而使用 REGEXP 解决方案则需要 30 多个小时。谢谢,下次见! :)

不要在SQL中这样做;使用 PHP 或其他具有用于解析 JSON.

的内置工具的语言来完成