Упростить транзакцию

Я использую Spring с PostgreSQL, и я пытаюсь сделать что-то вроде UPSERT, используя такой код:

jt.update("delete from A where id = 1")
jt.update("insert into A (id, value) values (1, 100)")

заключенный внутри транзакции (используя @Transactional).

Проблема заключается в том, что при наличии множества параллельных запросов этот код не работает с ошибками "дубликат ключа", то есть транзакция не изолирована или...

Я пропустил что-то о том, как работают транзакции? Должен ли я использовать другой механизм здесь (например, синхронизация потоков)?

Ответ 1

Я написал довольно большой блог-пост об этом, поэтому, хотя я мог бы получить downvotes для ссылок, прочитайте this.

Суть в том, что транзакции здесь не помогают (по крайней мере, по умолчанию), и, хотя можно написать правильный upsert, это на самом деле довольно сложно.

Ответ 2

Предполагая эту простую таблицу:

CREATE TABLE tbl(id int primary key, value int);

Эта функция почти на 100% безопасна (см. комментарии) для параллельных транзакций.:

CREATE OR REPLACE FUNCTION f_upsert(_id int, _value int)
  RETURNS void AS
$func$
BEGIN
LOOP
   UPDATE tbl SET value = _value WHERE  id = _id;

   EXIT WHEN FOUND;

   BEGIN
      INSERT INTO tbl (id, value)
      VALUES (_id, _value);

      RETURN;

   EXCEPTION WHEN UNIQUE_VIOLATION THEN     -- tbl.id has UNIQUE constraint.
      RAISE NOTICE 'It actually happened!'; -- hardly ever happens
   END;

END LOOP;
END
$func$ LANGUAGE plpgsql;

Вызов:

SELECT f_upsert(2, 2);

Он очень похож на этот случай INSERT / SELECT с большим количеством объяснений и ссылок: