sql >> Base de Datos >  >> RDS >> PostgreSQL

postgresql:el script que usa bloques de transacciones no puede crear todos los registros

Sí, algo estás haciendo mal.
Mira un ejemplo sencillo.

Sesión 1

postgres=# select * from user_reservation_table;
 id | usedyesno | userid | uservalue
----+-----------+--------+-----------
  1 | f         |      0 |         1
  2 | f         |      0 |         2
  3 | f         |      0 |         3
  4 | f         |      0 |         4
  5 | f         |      0 |         5
(5 wierszy)


postgres=# \set user 1
postgres=#
postgres=# begin;
BEGIN
postgres=# UPDATE user_reservation_table
postgres-# SET UsedYesNo = true, userid=:user
postgres-# WHERE uservalue IN(
postgres(#    SELECT uservalue FROM user_reservation_table
postgres(#    WHERE UsedYesNo=false Order By id ASC Limit 1)
postgres-# RETURNING uservalue;
 uservalue
-----------
         1
(1 wiersz)


UPDATE 1
postgres=#


Sesión 2 - al mismo tiempo, pero solo 10 ms después

postgres=# \set user 2
postgres=# begin;
BEGIN
postgres=# UPDATE user_reservation_table
postgres-# SET UsedYesNo = true, userid=:user
postgres-# WHERE uservalue IN(
postgres(#    SELECT uservalue FROM user_reservation_table
postgres(#    WHERE UsedYesNo=false Order By id ASC Limit 1)
postgres-# RETURNING uservalue;

La sesión 2 cuelga....... y está esperando algo....

de vuelta en Sesión 1

postgres=# commit;
COMMIT
postgres=#



y otra vez Sesión 2

 uservalue
-----------
         1
(1 wiersz)


UPDATE 1
postgres=# commit;
COMMIT
postgres=#

La sesión 2 ya no espera y finaliza su transacción.

¿Y cuál es el resultado final?:

postgres=# select * from user_reservation_table order by id;
 id | usedyesno | userid | uservalue
----+-----------+--------+-----------
  1 | t         |      2 |         1
  2 | f         |      0 |         2
  3 | f         |      0 |         3
  4 | f         |      0 |         4
  5 | f         |      0 |         5
(5 wierszy)

Dos usuarios tomaron el mismo valor 1, pero solo el usuario 2 está registrado en la tabla





======================EDITAR ==================================

En este escenario, podemos usar SELECCIONAR .. PARA ACTUALIZAR y utilizar una forma en la que postgre reevalúe la consulta en modo de nivel de aislamiento de lectura confirmada,
consulte la documentación:http://www.postgresql.org/docs/9.2/static/transaction-iso.html

En resumen:
si una sesión bloqueó la fila y la otra sesión está tratando de bloquear la misma fila, entonces la segunda sesión se "bloqueará" y esperará a que la primera sesión se confirme o retroceda. Cuando la primera sesión confirma la transacción, luego la segunda sesión reevaluará la condición de búsqueda DONDE. Si la condición de búsqueda no coincide (porque la primera transacción cambió algunas columnas), entonces la segunda sesión omitirá esa fila y procesará una fila siguiente que coincida con DONDE condiciones.

Nota:este comportamiento es diferente en los niveles de aislamiento de lectura repetible. En ese caso, la segunda sesión generará un error:no se pudo serializar el acceso debido a una actualización simultánea y debe volver a intentar toda la transacción.

Nuestra consulta puede parecerse a:

select id from user_reservation_table
where usedyesno = false
order by id
limit 1
for update ;

y luego:

  Update .... where id = (id returned by SELECT ... FOR UPDATE)



Personalmente, prefiero probar el escenario de bloqueo usando clientes de consola simples y antiguos (psql para postgree, mysql o SQLPlus para Oracle)

Así que probemos nuestra consulta en psql:

session1 #select * from user_reservation_table order by id;
 id | usedyesno | userid | uservalue
----+-----------+--------+-----------
  1 | t         |      2 |         1
  2 | f         |      0 |         2
  3 | f         |      0 |         3
  4 | f         |      0 |         4
  5 | f         |      0 |         5
(5 wierszy)


session1 #begin;
BEGIN
session1 #select id from user_reservation_table
postgres-# where usedyesno = false
postgres-# order by id
postgres-# limit 1
postgres-# for update ;
 id
----
  2
(1 wiersz)


session1 #update user_reservation_table set usedyesno = true
postgres-# where id = 2;
UPDATE 1
session1 #

La sesión 1 bloqueó y actualizó una fila id=2

Y ahora la sesión2

session2 #begin;
BEGIN
session2 #select id from user_reservation_table
postgres-# where usedyesno = false
postgres-# order by id
postgres-# limit 1
postgres-# for update ;

La sesión 2 se cuelga al intentar bloquear la identificación de la fila =2

OK, confirmemos la sesión 1

session1 #commit;
COMMIT
session1 #

y mira lo que pasa en la sesión 2:

postgres-# for update ;
 id
----
  3
(1 wiersz)

Bingo:la sesión 2 omitió el ID de fila =2 y seleccionó (y bloqueó) el ID de fila =3


Así que nuestra consulta final podría ser:

update user_reservation_table
set usedyesno = true
where id = (
   select id from user_reservation_table
   where usedyesno = false
   order by id
   limit 1
   for update
) RETURNING uservalue;

Alguna reserva:este ejemplo es solo para su propósito de prueba y su propósito es ayudar a comprender cómo funciona el bloqueo en postgre.
De hecho, esta consulta serializará el acceso a la tabla, y no es escalable y probablemente funcionará malo (lento) en un entorno multiusuario.
Imagine que 10 sesiones intentan obtener la siguiente fila de esta tabla simultáneamente; cada sesión se bloqueará y esperará hasta que la sesión anterior se confirme.
Así que no use esta consulta en el código de producción.
¿Realmente desea "buscar y reservar el siguiente valor de la tabla"? ¿Por qué?
En caso afirmativo, debe tener algún dispositivo de serialización (como esta consulta o, tal vez más fácil de implementar, bloquear toda la tabla), pero esto será un cuello de botella.