【问题标题】:How to properly loop in a stored function on MySQL?如何在 MySQL 上正确循环存储函数?
【发布时间】:2011-10-25 06:41:40
【问题描述】:

我在正确获取一个非常简单的存储过程时遇到了一些困难。 考虑以下文章表 sn-p:

id    replaced_by     baseID
 1              2          0
 2              3          0
 3              0          0

一个简单的分层表,使用写时复制。编辑文章时,当前文章的 replace_by 字段设置为新副本的 id。

我添加了一个 baseID 字段,将来应该存储文章的 baseID。 在我上面的示例中,有一篇文章(例如 id 3)。它的 baseID 为 1。

为了获取 baseID,我创建了以下存储过程:

DELIMITER $$

CREATE FUNCTION getBaseID(articleID INT) RETURNS INT
BEGIN
    DECLARE x INT;
    DECLARE y INT;
    SET x = articleID;
    sloop:LOOP
        SELECT id INTO y FROM article WHERE replaced_by_articleID = x;
        IF y IS NOT NULL THEN
            SET x = y;
            ITERATE sloop;
        ELSE
            LEAVE sloop;
        END IF;  
    END LOOP;
    RETURN x;
END $$

DELIMITER ;

看起来很简单,直到我真正使用以下方法调用该函数:

SELECT getBaseID(3);

我希望函数返回 1。我什至愿意理解它可能需要一秒钟的时间。 相反,机器的 CPU 会上升到 100% (mysqld)。

我什至使用REPEAT .. UNTILWHILE .. DO 重写了相同的函数,最终结果相同。

谁能解释为什么我的 CPU 在进入循环时会上升 100%?

旁注:我只是想赢得时间。我在 PHP 中创建了完全相同的函数,它运行良好,但我们的猜测是 MySQL 可以稍微快一点。我们需要筛选大约 1800 万条记录。我能节省的任何时间都是值得的。

提前感谢您的任何帮助和/或指点。


已解决的 SQL:

DELIMITER $$

CREATE FUNCTION getBaseID(articleID INT) RETURNS INT
BEGIN
    DECLARE x INT;
    DECLARE y INT;
    SET x = articleID;
    sloop:LOOP
        SET y = NULL;
        SELECT id INTO y FROM article WHERE replaced_by_articleID = x;
        IF y IS NULL THEN
            LEAVE sloop;
        END IF;  
        SET x = y;
        ITERATE sloop;
    END LOOP;
    RETURN x;
END $$

DELIMITER ;

【问题讨论】:

    标签: mysql loops stored-functions


    【解决方案1】:

    来自mysql

    如果查询没有返回任何行,则会出现错误代码为 1329 的警告(无数据),并且变量值保持不变

    因此,当没有找到具有给定 x 的记录时,您有一个无限循环(y 保持不变) 尝试SET y = (SELECT id ....) 或在您的选择语句之前添加SET y = null(它应该是循环中的第一条语句)

    【讨论】:

    • 天哪,非常感谢。我确实在 INTO 位上阅读了该页面,但我不明白他们的意思是 vars not changed。
    【解决方案2】:

    感觉您可能缺少replaced_by 列上的索引。如果replaced_by 上没有索引,则每次迭代都在查看全表扫描。

    ALTER TABLE article ADD INDEX (replaced_by);
    

    您还应该在检索之前确保该行存在

    DELIMITER $$
    
    CREATE FUNCTION getBaseID(articleID INT) RETURNS INT
    BEGIN
        DECLARE x INT;
        DECLARE y INT;
        SET x = articleID;
        sloop:LOOP
            SELECT COUNT(1) INTO y FROM article WHERE replaced_by = x;
            IF y > 0 THEN
                SELECT id INTO y FROM article WHERE replaced_by = x;
                SET x = y;
            ELSE
                LEAVE sloop;
            END IF;  
        END LOOP;
        RETURN x;
    END $$
    
    DELIMITER ;
    

    两倍的 SQL 调用,但比抱歉更安全。

    试试看!!!

    【讨论】:

    • 是的,索引可用。但是搜索不存在的行是我正在测试的。它表明我何时找到了 baseID!
    猜你喜欢
    • 1970-01-01
    • 2021-05-27
    • 1970-01-01
    • 2021-02-28
    • 1970-01-01
    • 2021-08-02
    • 2019-02-15
    • 2015-10-02
    • 1970-01-01
    相关资源
    最近更新 更多