Tengo una base de datos Postgres que contiene detalles sobre grupos de servidores, como el estado del servidor ('activo', 'en espera', etc.). Los servidores activos en cualquier momento pueden necesitar conmutar por error a un modo de espera, y no me importa qué modo de espera se use en particular.
Quiero una consulta de base de datos para cambiar el estado de un modo de espera, SOLO UNO, y devolver la IP del servidor que se utilizará. La elección puede ser arbitraria: dado que el estado del servidor cambia con la consulta, no importa qué modo de espera esté seleccionado.
¿Es posible limitar mi consulta a una sola actualización?
Esto es lo que tengo hasta ahora:
UPDATE server_info SET status = 'active'
WHERE status = 'standby' [[LIMIT 1???]]
RETURNING server_ip;
A Postgres no le gusta esto. ¿Qué podría hacer de manera diferente?
postgresql
update
concurrency
queue
vastlysuperiorman
fuente
fuente
Respuestas:
Sin acceso de escritura concurrente
Materialice una selección en un CTE y únase a ella en la
FROM
cláusula delUPDATE
.Originalmente tenía una subconsulta simple aquí, pero eso puede eludir
LIMIT
ciertos planes de consulta como señaló Feike :O utilice una subconsulta poco correlacionada para el caso simple con
LIMIT
1
. Más simple, más rápido:Con acceso de escritura concurrente
Asumiendo el nivel de aislamiento predeterminado
READ COMMITTED
para todo esto. Los niveles de aislamiento más estrictos (REPEATABLE READ
ySERIALIZABLE
) aún pueden provocar errores de serialización. Ver:Bajo carga de escritura concurrente, agregue
FOR UPDATE SKIP LOCKED
para bloquear la fila para evitar condiciones de carrera.SKIP LOCKED
fue agregado en Postgres 9.5 , para versiones anteriores ver abajo. El manual:Si no queda una fila desbloqueada que califique, no sucede nada en esta consulta (no se actualiza ninguna fila) y obtiene un resultado vacío. Para operaciones no críticas, eso significa que has terminado
Sin embargo, las transacciones concurrentes pueden tener filas bloqueadas, pero luego no terminan la actualización (
ROLLBACK
u otras razones). Para estar seguro, ejecute una verificación final:SELECT
También ve filas bloqueadas. Si bien eso no regresatrue
, una o más filas aún se están procesando y las transacciones aún podrían revertirse. (O mientras tanto, se han agregado nuevas filas). Espere un poco, luego repita los dos pasos: (UPDATE
hasta que no recupere la fila;SELECT
...) hasta que lleguetrue
.Relacionado:
Sin
SKIP LOCKED
en PostgreSQL 9.4 o anteriorLas transacciones concurrentes que intentan bloquear la misma fila se bloquean hasta que la primera libera su bloqueo.
Si se revierte la primera, la siguiente transacción toma el bloqueo y continúa normalmente; otros en la cola siguen esperando.
Si se confirma por primera vez, la
WHERE
condición se vuelve a evaluar y si ya noTRUE
existe (status
ha cambiado) el CTE (algo sorprendente) no devuelve ninguna fila. No pasa nada. Ese es el comportamiento deseado cuando todas las transacciones desean actualizar la misma fila .Pero no cuando cada transacción quiere actualizar la siguiente fila . Y dado que solo queremos actualizar una fila arbitraria (o aleatoria ) , no tiene sentido esperar en absoluto.
Podemos desbloquear la situación con la ayuda de bloqueos de asesoramiento :
De esta manera, la siguiente fila no bloqueada aún se actualizará. Cada transacción obtiene una nueva fila para trabajar. Recibí ayuda de la República Checa Postgres Wiki para este truco.
id
siendo cualquierbigint
columna única (o cualquier tipo con una conversión implícita comoint4
oint2
).Si se usan bloqueos de aviso para varias tablas en su base de datos al mismo tiempo, desambigüe con
pg_try_advisory_xact_lock(tableoid::int, id)
-id
siendo únicointeger
aquí.Como
tableoid
es unabigint
cantidad, teóricamente puede desbordarseinteger
. Si eres lo suficientemente paranoico, úsalo(tableoid::bigint % 2147483648)::int
en su lugar, dejando una "colisión hash" teórica para el verdadero paranoico ...Además, Postgres es libre de probar las
WHERE
condiciones en cualquier orden. Se podría probarpg_try_advisory_xact_lock()
y adquirir un bloqueo antesstatus = 'standby'
, lo que podría dar lugar a bloques de consulta adicionales en filas no relacionadas, en dondestatus = 'standby'
no es cierto. Pregunta relacionada sobre SO:Por lo general, puedes ignorar esto. Para garantizar que solo las filas calificadas estén bloqueadas, puede anidar el predicado (s) en un CTE como el anterior o una subconsulta con el
OFFSET 0
pirateo (evita la inserción) . Ejemplo:O (más barato para escaneos secuenciales) anida las condiciones en una
CASE
declaración como:Sin embargo, el
CASE
truco también evitaría que Postgres use un índicestatus
. Si dicho índice está disponible, no necesita anidamiento adicional para comenzar: solo las filas calificadas se bloquearán en un escaneo de índice.Como no puede estar seguro de que se use un índice en cada llamada, puede simplemente:
El
CASE
es lógicamente redundante, pero sirve al propósito discutido.Si el comando es parte de una transacción larga, considere los bloqueos a nivel de sesión que pueden (y deben) liberarse manualmente. Por lo tanto, puede desbloquear tan pronto como haya terminado con la fila bloqueada:
pg_try_advisory_lock()
ypg_advisory_unlock()
. El manual:Relacionado:
fuente