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

时间:2016-06-20 19:34:59

标签: mysql regex algorithm sqlperformance

序:

我已经做了很多(重新)搜索,发现以下SO帖子/回答:https://stackoverflow.com/a/5361490/6095216这与我正在寻找的非常接近。相同的代码,但有一些更有帮助的评论,显示在这里:http://thenoyes.com/littlenoise/?p=136

问题描述:

我需要将1列MySQL TEXT数据拆分成多列,其中原始数据具有这种格式(N <= 7):

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

正如您可能猜到的,我只需要提取,将每个值放入一个单独的(预定义)列中。问题是对于所有记录,字段的数量和顺序不保证是相同的。因此,使用SUBSTR / LOCATE等的解决方案不起作用,我需要使用正则表达式。另一个限制是不能使用第三方库,如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查询。它工作正常,但它。 IS。 SLOOOOOOOW ...只有7个字段/列可以拆分,最大(并非所有记录都有7个)!限制为20条记录,大约需要3分钟 - 我总共有大约40,000条记录(数据库不是很多,对吧?!):)

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

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

2 个答案:

答案 0 :(得分:0)

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

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

答案 1 :(得分:0)

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