Существует состояние гонки с вашим первым выбором и первым обновлением:
- Представьте, что 2 разных потока выполняют шаг 1 одновременно, и оба получают доступ к одной и той же строке. В этот момент
is_locked = false
, так что оба могут выбрать одну и ту же строку. - На шаге 2 оба потока попытаются обновить эту строку. Поток 1 сразу же преуспеет, поток 2 будет заблокирован, пока транзакция потока 1 не будет зафиксирована.
- Как только поток 1 завершится, поток 2 снова установит
is_locked = true
в той же строке и снова обработает ту же учетную запись. - Это нежелательно, у вас не только есть поток, который должен был ждать, но вы обрабатывали одну и ту же учетную запись дважды.
Вместо этого Postgres имеет предложение под названием FOR UPDATE SKIP LOCKED
, что позволит вам выполнить шаг 1 следующим образом (шаги 2 и 3 остаются без изменений):
SELECT * FROM gas_accounts WHERE is_locked = false LIMIT 1 FOR UPDATE SKIP LOCKED
Обратите внимание, что вам даже не нужен order by random()
, поскольку это будет выберите следующую доступную строку, которая не заблокирована (ни вашим столбцом is_locked
, ни собственной блокировкой Postgres), так что вы можете считать, что это случайная строка.
Что этот пункт делает, чтобы дать каждому транзакция одна строка, которая в данный момент не заблокирована какой-либо другой транзакцией, поэтому состояние гонки полностью исчезает. Просто убедитесь, что вы выполняете SELECT и первое ОБНОВЛЕНИЕ в той же транзакции.
Дополнительное примечание:
Кроме того, обратите внимание, что если 3 оператора происходят в одной транзакции вам даже не нужно иметь столбец is_locked
. Просто с помощью FOR UPDATE SKIP LOCKED
строка останется заблокированной и невидимой для других транзакций, которые также используют это предложение.