Обновление, где условия гонки Postgres (читать committed)
Я пытаюсь написать запрос, который обновляет строку в таблице "утверждение" до активного состояния, только если у пользователя уже открыто не более двух активных утверждений. Поэтому для целостности данных очень важно, чтобы пользователь никогда имеет более двух активных утверждений, открытых в любой момент времени.
Я запускаю этот запрос в параллельной среде, поэтому возможно, что два процесса выполняют этот запрос одновременно. Я также запускаю его по умолчанию Read Committed
уровень изоляции.
мне интересно, открыт ли я для возможности того, что пользователь может иметь более двух активных утверждений, открытых в какой-то момент, из-за условия гонки между подзапросом и предложением update.
в то же время производительность не так важна для этого запроса, как целостность данных.
update claim
set is_active = '1'
where claim.id = %s
and (2 > (select count(*)
from claim as active_claim
where active_claim.user_id = %s
and active_claim.is_active = '1'))
1 ответов
да, вполне возможно, что это может привести к более чем двум активным утверждениям, потому что параллельные транзакции не могут видеть изменения друг друга, поэтому два или более параллельных выполнения будут видеть 2 утверждения, и оба будут продолжать обновлять свои целевые утверждения, чтобы сделать их активными.
см.: транзакции базы данных предотвращают условия гонки.
таблица блокировок
самый простой вариант просто:
BEGIN;
LOCK TABLE claim IN EXCLUSIVE MODE;
UPDATE ...
COMMIT;
... но это довольно тяжелое решение.
блокировка уровня строки на объекте пользователя
предполагая, что у вас есть таблица user
для владельца претензий, вы должны вместо этого:
SELECT 1 FROM user WHERE user_id = whatever FOR UPDATE
в той же транзакции, перед запуском UPDATE
. Таким образом, вы будете держать эксклюзивную блокировку строк на пользователе и других SELECT ... FOR UPDATE
операторы будут блокировать на замок. Этот замок также будет блокировать UPDATE
S для удаления user
; он будет не block plain SELECT
s пользователя без FOR UPDATE
или FOR SHARE
предложения.
посмотреть явная блокировка в руководстве PostgreSQL.
SERIALIZABLE
изоляции
альтернативой является использование SERIALIZABLE
изоляция; PostgreSQL 9.2 и новее имеют обнаружение зависимостей транзакций, которое приведет к прерыванию всех конфликтующих транзакций, кроме одной, с ошибкой сериализации в приведенном выше примере. Так ваше приложение должно помнить, что оно пыталось сделать при запуске транзакции, и иметь возможность ловить ошибки, обнаруживать, что они являются сбоями сериализации, и повторять попытку после сбоя сериализации.
посмотреть изоляция транзакций в руководстве PostgreSQL.
консультативный замки
иногда нет хорошего объекта-кандидата для блокировки строки, и по какой-то причине или другая сериализуемая изоляция не решит проблему или не будет использоваться для другая цель. Это не для вас, это просто для общей информации.
в таких случаях вы можете использовать консультативные блокировки PostgreSQL для блокировки произвольных числовых значений; в этом случае вы бы pg_advisory_xact_lock(active_claim.user_id)
например. Глава явная блокировка содержит больше информации.