Как сохранить блокировку postgreSQL из триггера «перед обновлением» через саму операцию обновления - PullRequest
0 голосов
/ 24 сентября 2018

Я прошу прощения, если это ответ на вопрос, я провел некоторое исследование и не смог найти ответ.

Я поддерживаю структуру, похожую на папку / файл, в моем коде, где я заказал элементыэтот каскадный порядок изменяется при операциях обновления и удаления.Однако эти триггеры должны блокировать строки, чтобы гарантировать, что изменения порядка завершены, и продолжать блокировку до завершения операции

. Процесс обновления является относительно простым.Это управляющий псевдокод для всей операции:

check if pg_trigger_depth() >= 1
    return because this was a cascaded update from a trigger

lock the table for update on items with the old folder_parent_id
lock the table for update on items with the new folder_parent_id

update the old rows setting order_number -= 1 where the order_number is > the old order_number, and the folder_parent_id is the same as the old one
update the new rows setting order_number +=1 where the order_number is >= the new order_number and the folder_parent_id is the same as the new one

allow the update operation to go through (setting the order_number/folder_parent_id of this row to its new location)


release the lock for update on items with the old folder_parent_id
release the lock for update on items with the new folder_parent_id

Если блокировка снята до того, как фактическая операция завершена, могут возникнуть условия такого рода гонки.В этом примере задачи два обновления вызываются одновременно:

Для заданных дочерних элементов папки: a (0), b (1), c (2), d (3), e (4)

буквы - идентифицирующие свойства, а цифры - порядковые номера

мы хотим выполнить следующие операции: c (2 -> 1), d (3 -> 0)

Вот график этих операций:

ДО ОБНОВЛЕНИЯ ПО С:

decrement everything > OLD c.order_number (d--, e--)
increment everything >= NEW c.order_number (b++, d++, e++)

ТЕКУЩЕЕ СОСТОЯНИЕ: a (0), b (2), c (2), d (3), e (4)

ДО ОБНОВЛЕНИЯ ПО d:

decrement everything > OLD d.order_number (e--)
increment everything > NEW d.order_number (a++, b++, c++, e++)

СОВРЕМЕННОЕ СОСТОЯНИЕ: a (1), b (3), c (3), d (3), e (4)

SET c = 1

SET d = 0

ОКОНЧАТЕЛЬНОЕ СОСТОЯНИЕ: d (0), a (1), c (1), b (3), e (4)

Понятно, что условием состязания здесь является тот факт, что c и d оба изменяют положение друг друга в списке, но если триггер перед операцией запускается на каждом из них до того, как произойдет изменение состояния, тооперации, которые они выполняют друг с другом, отбрасываются.

Существует ли простой способ убедиться, что на них сохраняются блокировкиТаблицы от начала до конца этой операции, или иным образом, чтобы сделать это таким образом, чтобы исправить условия такого рода гонки?Я рассматривал возможность создания отдельной таблицы File_Structure_Lock, которая была бы заблокирована для обновления в триггере до, а затем разблокирована в триггере после, чтобы обойти систему блокировки PostgreSQL, но я решил, что должен быть лучший метод.

РЕДАКТИРОВАТЬ: меня спросили фактический SQL.Моя проблема здесь в подготовке к рефакторингу в коде, который уже существовал из-за того, что у этого кода были условия гонки, которые вызывали ошибки.Я могу попытаться отметить это через минуту, но вот необработанный код, с которым я работаю, с несколькими изменениями имени переменной, чтобы сделать его более понятным

CREATE OR REPLACE FUNCTION getOrderLock() RETURNS TRIGGER AS $getOrderLock$
BEGIN
    PERFORM * FROM Folders FOR UPDATE;
    PERFORM * FROM Files FOR UPDATE;
    IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
        RETURN NEW;
        ELSIF (TG_OP = 'DELETE') THEN
        RETURN OLD;
    END IF;
END;
$getOrderLock$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_folder_lock_rows
BEFORE INSERT OR UPDATE OR DELETE ON Folders
FOR EACH STATEMENT
WHEN (pg_trigger_depth() < 1)
EXECUTE PROCEDURE getOrderLock();

CREATE TRIGGER trigger_file_lock_rows
BEFORE INSERT OR UPDATE OR DELETE ON Files
FOR EACH STATEMENT
WHEN (pg_trigger_depth() < 1)
EXECUTE PROCEDURE getOrderLock();


CREATE OR REPLACE FUNCTION adjust_order_numbers_after_folder_update() RETURNS TRIGGER AS $adjust_order_numbers_after_nav_update$
BEGIN
    --update old location
    UPDATE Folders
        SET order_number = Folders.order_number - 1
        WHERE Folders.order_number >= OLD.order_number
        AND Folders.page_id = OLD.page_id
        AND COALESCE(Folders.folder_parent_id, 0) = COALESCE(OLD.folder_parent_id, 0)
        AND Folders.id != NEW.id;

    UPDATE Files
        SET order_number = Files.order_number - 1
        WHERE Files.order_number >= OLD.order_number
        AND Files.page_id = OLD.page_id
        AND COALESCE(Files.folder_parent_id, 0) = COALESCE(OLD.folder_parent_id, 0);

    --update new location
    UPDATE Folders
        SET order_number = Folders.order_number + 1
        WHERE Folders.order_number >= NEW.order_number
        AND Folders.page_id = NEW.page_id
        AND COALESCE(Folders.folder_parent_id, 0) = COALESCE(NEW.folder_parent_id, 0)
        AND Folders.id != NEW.id;

    UPDATE Files
        SET order_number = Files.order_number + 1
        WHERE Files.order_number >= NEW.order_number
        AND Files.page_id = NEW.page_id
        AND COALESCE(Files.folder_parent_id, 0) = COALESCE(NEW.folder_parent_id, 0);

    RETURN NEW;
END;
$adjust_order_numbers_after_nav_update$ LANGUAGE plpgsql;


CREATE OR REPLACE FUNCTION adjust_order_numbers_after_file_update() RETURNS TRIGGER AS $adjust_order_numbers_after_file_update$
BEGIN

    --update old location
    UPDATE Folders
        SET order_number = Folders.order_number - 1
        WHERE Folders.order_number >= OLD.order_number
        AND Folders.page_id = OLD.page_id
        AND COALESCE(Folders.folder_parent_id, 0) = COALESCE(OLD.folder_parent_id, 0);

    UPDATE Files
        SET order_number = Files.order_number - 1
        WHERE Files.order_number >= OLD.order_number
        AND Files.page_id = OLD.page_id
        AND COALESCE(Files.folder_parent_id, 0) = COALESCE(OLD.folder_parent_id, 0)
        AND Files.id != NEW.id;

    --update new location
    UPDATE Folders
        SET order_number = Folders.order_number + 1
        WHERE Folders.order_number >= NEW.order_number
        AND Folders.page_id = NEW.page_id
        AND COALESCE(Folders.folder_parent_id, 0) = COALESCE(NEW.folder_parent_id, 0);

    UPDATE Files
        SET order_number = Files.order_number + 1
        WHERE Files.order_number >= NEW.order_number
        AND Files.page_id = NEW.page_id
        AND COALESCE(Files.folder_parent_id, 0) = COALESCE(NEW.folder_parent_id, 0)
        AND Files.id != NEW.id;

    RETURN NEW;
END;
$adjust_order_numbers_after_file_update$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_folder_order_shift
AFTER UPDATE ON Folders
FOR EACH ROW 
WHEN (
    (
        COALESCE(OLD.folder_parent_id, 0) != COALESCE(NEW.folder_parent_id, 0)
        OR OLD.order_number != NEW.order_number
        OR Old.page_id != New.page_id
    )
    AND pg_trigger_depth() < 1
)
EXECUTE PROCEDURE adjust_order_numbers_after_folder_update();

CREATE TRIGGER trigger_file_order_shift
AFTER UPDATE ON Files
FOR EACH ROW 
WHEN (
    (
        COALESCE(OLD.folder_parent_id, 0) != COALESCE(NEW.folder_parent_id, 0)
        OR OLD.order_number != NEW.order_number
        OR Old.page_id != New.page_id
    )
    AND pg_trigger_depth() < 1
)
EXECUTE PROCEDURE adjust_order_numbers_after_file_update();

1 Ответ

0 голосов
/ 26 сентября 2018

Проблема, похоже, исходит от order_number, который вы настаиваете на том, чтобы быть последовательностью целых чисел без пробелов, упорядочивающей элементы в каждой папке.Если вы хотите сохранить это, вы должны перетасовать все элементы вокруг, и это действительно трудно сделать без какой-либо серьезной блокировки.

Но если все, что вы хотите сделать, это поддерживать определенный порядок элементовЯ бы ослабил требование последовательности без пробелов и вместо этого использовал бы значения double precision для описания порядка элементов.Тогда легко вставить элемент в любое место без изменения order_number в любом другом элементе - вы всегда можете присвоить перемещенному элементу order_number, который находится между любыми двумя существующими.

...