PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

postgresql - lo script che utilizza i blocchi di transazione non riesce a creare tutti i record

Sì, stai sbagliando qualcosa.
Guarda un semplice esempio.

Sessione 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=#


Sessione 2 - contemporaneamente, ma solo 10 ms dopo

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 sessione 2 si blocca ....... ed è in attesa di qualcosa ....

di nuovo nella sessione 1

postgres=# commit;
COMMIT
postgres=#



e ancora Sessione 2

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


UPDATE 1
postgres=# commit;
COMMIT
postgres=#

La sessione 2 non è più in attesa e termina la transazione.

E qual è il risultato finale?:

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)

Due utenti hanno preso lo stesso valore 1, ma solo l'utente 2 è registrato nella tabella





=======================EDIT ====================================

In questo scenario possiamo utilizzare SELECT .. FOR UPDATE e utilizzare un modo in cui Postgre rivaluta la query in modalità Read Committed Isolation Level,
consultare la documentazione:http://www.postgresql.org/docs/9.2/static/transaction-iso.html

In breve:
se una sessione ha bloccato la riga e l'altra sessione sta tentando di bloccare la stessa riga, la seconda sessione si "bloccherà" e attenderà il commit o il rollback della prima sessione. Quando la prima sessione esegue il commit della transazione, la seconda sessione rivaluta la condizione di ricerca WHERE. Se la condizione di ricerca non corrisponde (perché la prima transazione ha modificato alcune colonne), la seconda sessione salterà quella riga ed elaborerà una riga successiva che corrisponde a WHERE condizioni.

Nota:questo comportamento è diverso nei livelli di isolamento in lettura ripetibili. In tal caso, la seconda sessione genererà un errore:impossibile serializzare l'accesso a causa di un aggiornamento simultaneo e devi riprovare l'intera transazione.

La nostra query potrebbe assomiglia a:

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

e poi:

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



Personalmente, preferisco testare lo scenarious di blocco utilizzando semplici client console vecchi (psql per postgree, mysql o SQLPlus per oracle)

Quindi testiamo la nostra query in 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 sessione 1 ha bloccato e aggiornato una riga id=2

E ora la sessione2

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 sessione 2 si blocca durante il tentativo di bloccare l'ID riga =2

OK, esegui il commit della sessione 1

session1 #commit;
COMMIT
session1 #

e guarda cosa succede nella sessione 2:

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

Bingo - la sessione 2 ha saltato la riga id =2 e ha selezionato (e bloccato) la riga id =3


Quindi la nostra domanda finale potrebbe essere:

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;

Qualche riserva:questo esempio è solo a scopo di test e lo scopo è aiutare a capire come funziona il blocco in postgre.
In effetti, questa query serializzerà l'accesso alla tabella e non è scalabile e probabilmente funzionerà errato (lento) in un ambiente multiutente.
Immagina che 10 sessioni stiano tentando contemporaneamente di ottenere la riga successiva da questa tabella:ogni sessione si bloccherà e attenderà fino al commit della sessione precedente.
Quindi non utilizzare questa query nel codice di produzione.
Vuoi davvero "trovare e prenotare il valore successivo dalla tabella"? Perché?
Se sì, è necessario disporre di un dispositivo di serializzazione (come questa query o, forse più semplice da implementare, bloccare l'intera tabella), ma questo sarà un collo di bottiglia.