Обновление, где условия гонки 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 операторы будут блокировать на замок. Этот замок также будет блокировать UPDATES для удаления user; он будет не block plain SELECTs пользователя без FOR UPDATE или FOR SHARE предложения.

посмотреть явная блокировка в руководстве PostgreSQL.

SERIALIZABLE изоляции

альтернативой является использование SERIALIZABLE изоляция; PostgreSQL 9.2 и новее имеют обнаружение зависимостей транзакций, которое приведет к прерыванию всех конфликтующих транзакций, кроме одной, с ошибкой сериализации в приведенном выше примере. Так ваше приложение должно помнить, что оно пыталось сделать при запуске транзакции, и иметь возможность ловить ошибки, обнаруживать, что они являются сбоями сериализации, и повторять попытку после сбоя сериализации.

посмотреть изоляция транзакций в руководстве PostgreSQL.

консультативный замки

иногда нет хорошего объекта-кандидата для блокировки строки, и по какой-то причине или другая сериализуемая изоляция не решит проблему или не будет использоваться для другая цель. Это не для вас, это просто для общей информации.

в таких случаях вы можете использовать консультативные блокировки PostgreSQL для блокировки произвольных числовых значений; в этом случае вы бы pg_advisory_xact_lock(active_claim.user_id) например. Глава явная блокировка содержит больше информации.