#postgresql #triggers #race-condition
#postgresql #триггеры #состояние гонки
Вопрос:
Я использую экземпляр PostgreSQL RDS в AWS. В принципе, есть запрос, который вставляет данные в первую таблицу, давайте назовем это table
. Данные там могут иметь дубликаты в некоторых полях (очевидно, за исключением первичного ключа).
Затем есть триггер, который обновляет другую таблицу infotable
, не допуская дублирования.
Триггер:
CREATE TRIGGER insert_infotable AFTER INSERT ON table
FOR EACH ROW EXECUTE PROCEDURE insert_infotable();
Соответствующая часть функции триггера выглядит следующим образом:
CREATE OR REPLACE FUNCTION insert_infotable() RETURNS trigger AS $insert_infotable$
BEGIN
--some irrelevant code
IF NOT EXISTS (SELECT * FROM infotable WHERE col1 = NEW.col1 AND col2 = NEW.col2) THEN
INSERT INTO infotable(col1, col2, col3, col4, col5, col6) values (--some values--);
END IF;
RETURN NEW;
END;
$insert_infotable$ LANGUAGE plpgsql;
Таблица infotable
имеет УНИКАЛЬНОЕ ограничение на столбцы col1
и col2
.
В целом все работает нормально, но редко, примерно раз в 1 тыс. вставок, триггер возвращает ошибку «дублирующее значение ключа нарушает уникальное ограничение «unique_col1_and_col2″» для таблицы infotable
. Чего не должно произойти, поскольку в функции триггера есть IF NOT EXISTS
часть.
Первый вопрос заключается в том, что может быть причиной этого? Единственное, о чем я могу думать, это гонки, в которых два пользователя одновременно получают одну и ту же информацию, оба запускают триггер, но затем один обновляет вторую таблицу с помощью триггера, а второй пользователь получает ошибку дублирования. И из-за этого весь его запрос insert завершается неудачей, включая вставку в main table
.
Если это так, что я могу с этим поделать? Является ли использование блокировки при вставке хорошей идеей для таблицы, в которой предполагается, что более 100 пользователей одновременно вставляют данные?
И если да, то какой тип блокировки мне следует использовать и какую таблицу мне следует заблокировать — основную таблицу или вторую, которая изменяется триггером? (или, я думаю, у меня должна быть блокировка с моим основным оператором insert или внутри функции триггера?)
Ответ №1:
Да, это условие гонки. Два таких триггера, запущенных одновременно, не будут видеть изменения друг друга, поскольку транзакции еще не зафиксированы.
Поскольку у вас есть уникальное ограничение на infotable
, вы можете просто использовать
INSERT INTO infotable ...
ON CONFLICT (col1, col2) DO NOTHING;