выбрать и заблокировать случайную строку из postgresql приложения Java - PullRequest
1 голос
/ 23 марта 2020

У меня есть ситуация, когда у меня есть таблица с около 500 учетными записями. Эти счета используются для выполнения некоторых финансовых транзакций. Когда транзакция начинается, мне нужно получить учетную запись из этой таблицы (любая произвольная учетная запись будет работать), а затем заблокировать эту строку, чтобы никакая другая строка не имела доступа к этой учетной записи до завершения операции. Затем отправьте транзакцию во внешнюю систему и разблокируйте учетную запись.

Я реализовал это с помощью следующего алгоритма. Здесь все запросы выполняются в транзакционном контексте.

//Get Random account
1. select * from gas_accounts where is_locked = false order by random() limit 1
//Lock that account using the primary key
2. update gas_accounts set is_locked = true, last_updated_at = current_timestamp where public_key = :publicKey
//unlock the account
3. update gas_accounts set is_locked = false, last_updated_at = current_timestamp where public_key = :publicKey

При работе с 50 одновременными потоками, для выполнения вышеприведенной функции требуется около 50 секунд. Есть ли лучший способ сделать это?

1 Ответ

1 голос
/ 23 марта 2020

Существует состояние гонки с вашим первым выбором и первым обновлением:

  • Представьте, что 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 строка останется заблокированной и невидимой для других транзакций, которые также используют это предложение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...