Как избежать состояния гонки при последовательном удалении и вставке в postgres - PullRequest
0 голосов
/ 23 апреля 2019

У меня есть четыре таблицы:

  1. Участник [Столбцы: Id, Имя]
  2. Ответ [Столбцы: Id, Question_id (FK таблицы вопросов), Option_Id (FK ofТаблица параметров), Participant_Id (FK таблицы участников)].
  3. Вопрос [Id, заголовок]
  4. Параметр [Id, title, question_id (FK таблицы вопросов)].

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

Теперь при сохранении последних мы удаляем существующие ответы для текущего участника и вставляем последние ответы.

Теперь может возникнуть сценарий, когда в 2 ветке сохраняются ответы для одного и того же участника, поэтому оба будут пытатьсяудалите ответы, а затем вставьте.Если оба потока удаляются одновременно, а затем пытаются вставить его, это приводит к состоянию гонки, и дублирующиеся ответы будут сохранены.

Подробнее:

  1. Существует 4 существующих ответа длятекущий участник.
  2. Тема 1 и Тема 2 пришли для сохранения записей ответов 6 и 7.
  3. Тема 1: Удаленные ответы (удалено 4 существующих ответа).
  4. Тема 2: Удалить ответы (удалено 0 ответов, так как тема 1 уже удалена)
  5. Тема 1: вставить последние 6 ответов.
  6. Тема 2: вставить последние 7 ответов.

Здесь у текущего участника будет 6 + 7 = 13 ответ, который является неправильным, он должен быть либо 6, либо 7.

Как справиться с транзакцией или каким-либо другим подходом?Я не хочу использовать блокировку потока Java и не хочу блокировать всю таблицу.

Что-то, как я могу взять блокировку для member_id таблицы Ответов при выполнении операции удаления / вставки, так что другая транзакция должна ждать, еслиони пытаются вставить / обновить / удалить с тем же идентификатором участника?В поисках решения с лучшей производительностью.

Я использую Postgres SQL и JPA.Я готов использовать нормальный SQL для удаления и вставки, если требуется.

1 Ответ

1 голос
/ 23 апреля 2019

Вы можете получить явную блокировку для участника:

BEGIN;
SELECT FROM Participant WHERE Id = $PARTICIPANT_ID FOR NO KEY UPDATE;
DELETE FROM Answer WHERE Participant_Id = $PARTICIPANT_ID;
INSERT INTO Answer (Question_id, Option_Id, Participant_Id)
    VALUES ($QUESTION_ID, $OPTION_ID, $PARTICIPANT_ID);
COMMIT;

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

КомуУбедитесь сами, попробуйте это с двумя сессиями PSQL.В первом вы набираете:

BEGIN;
SELECT FROM Participant WHERE Id = $PARTICIPANT_ID FOR NO KEY UPDATE;

Во втором сеансе вы заметите, что обычный SELECT все еще работает:

SELECT * FROM Participant WHERE Id = $PARTICIPANT_ID;

Но получая такую ​​же блокировку вВторой сеанс будет зависать:

SELECT FROM Participant WHERE Id = $PARTICIPANT_ID FOR NO KEY UPDATE;

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

Если вы предпочитаете потерпеть неудачу рано, если блокировка недоступна, а не ожидает ее получения, вы можете использовать NOWAIT.Попробуйте сделать это сейчас во втором сеансе, пока транзакция еще продолжается в первом сеансе:

SELECT FROM Participant WHERE Id = $PARTICIPANT_ID FOR NO KEY UPDATE NOWAIT;

Вы получите немедленную ошибку:

ОШИБКА: не удалось получить блокировкув строке в отношении «Участник»

См. документы PostgreSQL о явном блокировании для получения дополнительной информации.

...