Триггер иногда завершается ошибкой с дублированием ключа

#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;