У меня есть база данных Postgres, которая содержит подробную информацию о кластерах серверов, таких как состояние сервера («активный», «резервный» и т. Д.). Активным серверам в любой момент может потребоваться переключение на резервный режим, и мне все равно, какой резервный режим используется в частности.
Я хочу, чтобы запрос к базе данных изменил статус резервного - ТОЛЬКО ОДИН - и вернул IP-адрес сервера, который должен быть использован. Выбор может быть произвольным: поскольку состояние сервера изменяется с запросом, не имеет значения, какой резервный режим выбран.
Можно ли ограничить мой запрос только одним обновлением?
Вот что у меня так далеко:
UPDATE server_info SET status = 'active'
WHERE status = 'standby' [[LIMIT 1???]]
RETURNING server_ip;
Постгресу это не нравится. Что я мог сделать по-другому?
postgresql
update
concurrency
queue
vastlysuperiorman
источник
источник
Ответы:
Без одновременного доступа к записи
Материализация выбора в CTE и присоединение к нему в
FROM
предложенииUPDATE
.Изначально у меня был простой подзапрос, но он может обойтись без
LIMIT
определенных планов запросов, как указал Фейке :Или используйте слабо коррелированный подзапрос для простого случая с
LIMIT
1
. Проще, быстрее:С одновременным доступом к записи
Предполагая уровень изоляции по умолчанию
READ COMMITTED
для всего этого. Более строгие уровни изоляции (REPEATABLE READ
иSERIALIZABLE
) могут по-прежнему приводить к ошибкам сериализации. Видеть:При одновременной загрузке записи добавьте
FOR UPDATE SKIP LOCKED
блокировку строки, чтобы избежать условий гонки.SKIP LOCKED
был добавлен в Postgres 9.5 , более старые версии см. ниже. Руководство:Если не осталось соответствующей незаблокированной строки, в этом запросе ничего не происходит (строка не обновляется), и вы получите пустой результат. Для некритических операций это означает, что вы сделали.
Тем не менее, параллельные транзакции могут иметь заблокированные строки, но затем не завершить обновление (
ROLLBACK
или по другим причинам). Чтобы быть уверенным, запустите финальную проверку:SELECT
также видит заблокированные строки. Если не возвращаетсяtrue
, одна или несколько строк все еще обрабатываются, и транзакции могут быть откатаны. (Или тем временем были добавлены новые строки.) Подождите немного, затем выполните цикл двух шагов: (UPDATE
пока вы не вернете строку;SELECT
...), пока не получитеtrue
.Связанный:
Без
SKIP LOCKED
в PostgreSQL 9.4 или старшеПараллельные транзакции, пытающиеся заблокировать одну и ту же строку, блокируются до тех пор, пока первая из них не снимет свою блокировку.
Если первый откатывался, следующая транзакция захватывает блокировку и продолжается в обычном режиме; другие в очереди продолжают ждать.
Если первый зафиксирован,
WHERE
условие переоценивается, и если оноTRUE
большеstatus
не изменяется ( изменилось), CTE (несколько неожиданно) не возвращает строки. Ничего не произошло. Это желаемое поведение, когда все транзакции хотят обновить одну и ту же строку .Но не тогда , когда каждая транзакция хочет обновить на следующую строку . А поскольку мы просто хотим обновить произвольную (или случайную ) строку , ждать вообще не имеет смысла.
Мы можем разблокировать ситуацию с помощью консультативных блокировок :
Таким образом, следующая не заблокированная строка будет обновлена. Каждая транзакция получает новую строку для работы. Мне помогала чешская Postgres Wiki для этого трюка.
id
быть любым уникальнымbigint
столбцом (или любым типом с неявным приведением типаint4
илиint2
).Если консультативные блокировки используются для нескольких таблиц в вашей базе данных одновременно, устраните неоднозначность с
pg_try_advisory_xact_lock(tableoid::int, id)
-id
быть уникальнымinteger
здесь.Поскольку
tableoid
этоbigint
количество, оно теоретически может переполнитьсяinteger
. Если вы достаточно параноик, используйте(tableoid::bigint % 2147483648)::int
вместо этого - оставив теоретическое «столкновение хешей» для действительно параноиков ...Кроме того, Postgres имеет право тестировать
WHERE
условия в любом порядке. Он может тестироватьpg_try_advisory_xact_lock()
и получать блокировку раньшеstatus = 'standby'
, что может привести к дополнительным консультативным блокировкам на несвязанных строках, гдеstatus = 'standby'
это не так. Связанный вопрос по SO:Как правило, вы можете просто игнорировать это. Чтобы гарантировать блокировку только подходящих строк, вы можете вложить предикаты в CTE, как описано выше, или подзапрос с
OFFSET 0
хаком (предотвращает встраивание) . Пример:Или (дешевле для последовательных сканирований) вложите условия в
CASE
утверждение вроде:Однако
CASE
трюк будет также держать Postgres использовать индекс наstatus
. Если такой индекс доступен, для начала вам не нужно дополнительное вложение: в проверке индекса будут блокироваться только подходящие строки.Поскольку вы не можете быть уверены, что индекс используется в каждом вызове, вы можете просто:
CASE
Логически лишний, но это сервера обсуждаемая цели.Если команда является частью длинной транзакции, рассмотрите блокировки на уровне сеанса, которые могут быть (и должны быть) сняты вручную. Таким образом, вы можете разблокировать, как только закончите с заблокированным рядом:
pg_try_advisory_lock()
иpg_advisory_unlock()
. Руководство:Связанный:
источник