Как правильно зациклить в хранимой функции на MySQL? - PullRequest
7 голосов
/ 11 августа 2011

У меня возникли некоторые затруднения с получением довольно простой правильной хранимой процедуры.Рассмотрим следующий фрагмент таблицы статей:

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

Простая иерархическая таблица, использующая копирование при записи.Когда статья редактируется, в поле replace_by текущей статьи устанавливается идентификатор ее новой копии.

Я добавил поле 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. Я даже готов понять, что это может занять долю секунды.Вместо этого ЦП машины увеличивается до 100% (mysqld).

Я даже переписал ту же функцию, используя REPEAT .. UNTIL и WHILE .. DO, с тем же конечным результатом.

МожетКто-нибудь объяснит, почему мой процессор поднимается на 100%, когда входит в цикл?

Примечание: я пытаюсь просто выиграть время.Я создал точно такую ​​же функцию в PHP, которая хорошо работает, но мы предполагаем, что MySQL может сделать это немного быстрее.Нам нужно просеять около 18 миллионов записей.Любое время, которое я могу сэкономить, будет стоить.

Заранее благодарим за любую помощь и / или указатели.


Решенный 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 ;

Ответы [ 2 ]

1 голос
/ 11 августа 2011

С mysql :

Если запрос не возвращает строк, появляется предупреждение с кодом ошибки 1329 (Нет данных) и значения переменной остаются неизменными

Таким образом, у вас есть бесконечный цикл, когда не найдено записей с данным x (y остается неизменным) Вместо этого попробуйте SET y = (SELECT id ....) или добавьте SET y = null перед оператором select (это должен быть первый операторпетля)

0 голосов
/ 12 августа 2011

Такое чувство, что вам может не хватать индекса в столбце replace_by.Если для replace_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-вызовы, но лучше, чем потом сожалеть.

Попробуйте! !!!

...