2017-02-09 18 views
2

У меня есть таблица ContentAddressedFiles, где комбинация столбцов hash, size и extension - UNIQUE. Я хочу создать хранимую процедуру, которая при вызове вставляет новую запись в таблицу с указанными значениями. Если запись для этих значений уже существует, я хочу просто вернуть эту существующую запись. Вот мой подход:Невозможно ВЫБРАТЬ причину УНИКАЛЬНОГО НАРУШЕНИЯ

CREATE OR REPLACE FUNCTION INIT_CAF(id_in_case_of_new UUID, _hash VARCHAR(255), _size INTEGER, _extension VARCHAR(255), _mimeType VARCHAR(255)) 
RETURNS "ContentAddressedFiles" 
AS $$ 
DECLARE 
    caf "ContentAddressedFiles"%ROWTYPE; 
BEGIN 

    INSERT INTO "ContentAddressedFiles" (id, hash, size, extension, "mimeType", "createdAt", "updatedAt") 
    VALUES(id_in_case_of_new, _hash, _size, _extension, _mimeType, NOW(), NOW()) RETURNING * INTO caf; 

    RETURN caf; 

EXCEPTION WHEN unique_violation THEN 

    SELECT * FROM "ContentAddressedFiles" INTO caf WHERE "hash" = _hash AND "size" = _size AND "extension" = _extension; 

    IF NOT FOUND THEN 
    RAISE EXCEPTION 'This should never happen.'; 
    END IF; 

    RETURN caf; 

END; 
$$ LANGUAGE plpgsql; 

Однако, когда я вызвать процедуру из параллельных транзакций, я постоянно получаю исключение:

EXCEPTION: This should never happen. 

Как это возможно? Процедура не кажется, чтобы быть в состоянии SELECT причину провала попытки INSERT раньше (это не id, что столкновения, это просто кортеж <hash, size, extension>

+0

так что вы видите поднятый exveption и нуль вместо ожидаемой строки? .. пожалуйста, добавьте выход у вас есть на вопрос –

+0

Да, именно я получаю исключение при запуске процедуры одновременно с различных операций. – DeX3

+0

http://stackoverflow.com/questions/6722344/select-or-insert-a-row-in-one-command –

ответ

1

вопрос, как избежать проблем ответа с комментариями;. I будет объяснять здесь почему PostgreSQL ведет себя в наблюдаемом пути.

причина заключается в том, что INSERT и SELECT заявления в функции увидеть различные снимок (состояний) баз данных, так как транзакция выполняется с по умолчанию уровень изоляции READ COMMITTED. На этом уровне изоляции каждый оператор получает новый снимок базы данных.

Объяснение наблюдаемого поведения должно быть то, что параллельная транзакция удаляет или изменяет строку между неудавшейся INSERT и следующим SELECT заявлением, так что строка, которая вызвала нарушение ограничения для INSERT больше не существует, когда SELECT запускается.

Есть два подхода, чтобы иметь дело с проблемой:

  • выбрать более высокий уровень изоляции: Тогда оба утверждения будут видеть один и тот же снимок базы данных, и строк, предотвращены INSERT будет найден SELECT, хотя он был изменен тем временем. Это не проблема, это просто означает, что вся транзакция логически происходит во время снятия моментального снимка.

  • Запускайте оба оператора как единый оператор с помощью CTE, как рекомендует решение, данное в комментариях. Затем они также будут видеть тот же снимок базы данных.

+0

Да, вы правы, это самый вероятный сценарий (даже если окно для состояния гонки крошечное). По крайней мере, для функций 'VOLATILE'; из 'STABLE', snapshotting отличается, но эта категория не позволит' INSERT' в любом случае. Возможно, отличным примером этого моментального снимка является [как пример исходного примера UPSERT] (https://www.postgresql.org/docs/current/static/plpgsql-control-structures.html#PLPGSQL-ERROR-TRAPPING). – pozs