sql >> Database >  >> RDS >> PostgreSQL

postgresql - script dat transactieblokken gebruikt, kan niet alle records maken

Ja, je doet iets verkeerd.
Kijk naar een eenvoudig voorbeeld.

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


Sessie 2 - tegelijkertijd, maar slechts 10 ms later

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;

Sessie 2 loopt vast ....... en wacht op iets ....

terug in Sessie 1

postgres=# commit;
COMMIT
postgres=#



en opnieuw Sessie 2

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


UPDATE 1
postgres=# commit;
COMMIT
postgres=#

Sessie 2 wacht niet meer en voltooit de transactie.

En wat is het uiteindelijke resultaat?:

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)

Twee gebruikers namen dezelfde waarde 1, maar alleen gebruiker 2 is geregistreerd in de tabel





======================BEWERKEN ==================================

In dit scenario kunnen we SELECT .. FOR UPDATE gebruiken en een manier gebruiken waarop postgre de query opnieuw evalueert in Read Committed Isolation Level-modus,
zie documentatie:http://www.postgresql.org/docs/9.2/static/transaction-iso.html

In het kort:
als de ene sessie de rij heeft vergrendeld en de andere sessie probeert dezelfde rij te vergrendelen, dan zal de tweede sessie "vastlopen" en wachten tot de eerste sessie wordt vastgelegd of teruggedraaid. Wanneer de eerste sessie de transactie begaat, zal de tweede sessie de WHERE-zoekvoorwaarde opnieuw evalueren. Als de zoekvoorwaarde niet overeenkomt (omdat de eerste transactie enkele kolommen heeft gewijzigd), zal de tweede sessie die rij overslaan en een volgende rij verwerken die overeenkomt met WHERE voorwaarden.

Opmerking:dit gedrag is anders in herhaalbare leesisolatieniveaus. In dat geval zal de tweede sessie de fout geven:kon toegang niet serialiseren vanwege gelijktijdige update, en u moet de hele transactie opnieuw proberen.

Onze vraag kan eruit zien als:

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

en dan:

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



Persoonlijk test ik liever vergrendelingsscenario's met gewone, oude consoleclients (psql voor postgree, mysql of SQLPlus voor oracle)

Dus laten we onze query testen 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 #

Sessie 1 vergrendeld en een rij bijgewerkt id=2

En nu sessie2

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

Sessie 2 loopt vast tijdens het vergrendelen van de rij-id =2

OK, laten we sessie 1 vastleggen

session1 #commit;
COMMIT
session1 #

en kijk wat er gebeurt in sessie 2:

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

Bingo - sessie 2 sloeg de rij-id =2 over en selecteerde (en vergrendelde) de rij-id =3


Dus onze laatste vraag zou kunnen zijn:

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;

Enige voorbehoud - dit voorbeeld is alleen voor uw testdoeleinden en het is bedoeld om te helpen begrijpen hoe vergrendeling in postgre werkt.
In feite zal deze query de toegang tot de tabel serialiseren en is niet schaalbaar en zal waarschijnlijk worden uitgevoerd slecht (langzaam) in een omgeving met meerdere gebruikers.
Stel je voor dat 10 sessies tegelijkertijd proberen om de volgende rij uit deze tabel te halen - elke sessie loopt vast en wacht tot de vorige sessie wordt vastgelegd.
Dus niet gebruiken deze query in productiecode.
Wilt u echt "de volgende waarde uit de tabel vinden en reserveren"? Waarom?
Zo ja, dan moet je een serialisatie-apparaat hebben (zoals deze query, of, misschien makkelijker te implementeren, de hele tabel vergrendelen), maar dit zal een knelpunt zijn.




  1. Hoe de primaire sleutel van de MySQL-tabel automatisch te laten toenemen met een of ander voorvoegsel?

  2. Hoe SQLite Avg() werkt

  3. Index maken in mysql werkt niet

  4. MYSQL EXACT woord selecteren met MATCH-query?