Я прошу прощения, если это ответ на вопрос, я провел некоторое исследование и не смог найти ответ.
Я поддерживаю структуру, похожую на папку / файл, в моем коде, где я заказал элементыэтот каскадный порядок изменяется при операциях обновления и удаления.Однако эти триггеры должны блокировать строки, чтобы гарантировать, что изменения порядка завершены, и продолжать блокировку до завершения операции
. Процесс обновления является относительно простым.Это управляющий псевдокод для всей операции:
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();