PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

postgresql - Skript, das Transaktionsblöcke verwendet, kann nicht alle Datensätze erstellen

Ja, Sie machen etwas falsch.
Schauen Sie sich ein einfaches Beispiel an.

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


Sitzung 2 - gleichzeitig, aber nur 10 ms später

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;

Sitzung 2 hängt ....... und wartet auf etwas ....

zurück in Sitzung 1

postgres=# commit;
COMMIT
postgres=#



und wieder Sitzung 2

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


UPDATE 1
postgres=# commit;
COMMIT
postgres=#

Sitzung 2 wartet nicht mehr und beendet ihre Transaktion.

Und was ist das Endergebnis?:

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)

Zwei Benutzer haben denselben Wert 1 angenommen, aber nur Benutzer 2 ist in der Tabelle registriert





=====================BEARBEITEN =================================

In diesem Szenario können wir SELECT .. FOR UPDATE verwenden und einen Weg nutzen, bei dem postgre die Abfrage im Read Committed Isolation Level-Modus neu auswertet,
siehe Dokumentation:http://www.postgresql.org/docs/9.2/static/transaction-iso.html

Kurz gesagt:
Wenn eine Sitzung die Zeile gesperrt hat und die andere Sitzung versucht, dieselbe Zeile zu sperren, bleibt die zweite Sitzung "hängen" und wartet darauf, dass die erste Sitzung festgeschrieben oder zurückgesetzt wird. Bei der ersten Sitzung die Transaktion festschreibt, wertet die zweite Sitzung die WHERE-Suchbedingung neu aus. Wenn die Suchbedingung nicht zutrifft (weil die erste Transaktion einige Spalten geändert hat), überspringt die zweite Sitzung diese Zeile und verarbeitet eine nächste Zeile, die mit WHERE übereinstimmt Bedingungen.

Hinweis:Dieses Verhalten unterscheidet sich bei Isolationsstufen für wiederholbares Lesen. In diesem Fall wird die zweite Sitzung einen Fehler ausgeben:Der Zugriff konnte aufgrund einer gleichzeitigen Aktualisierung nicht serialisiert werden, und Sie müssen die gesamte Transaktion wiederholen.

Unsere Abfrage kann so aussehen:

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

und dann:

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



Ich persönlich ziehe es vor, Sperrszenarien mit einfachen, alten Konsolen-Clients zu testen (psql für Postgree, mysql oder SQLPlus für Oracle)

Testen Sie also unsere Abfrage 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 #

Sitzung 1 gesperrt und aktualisiert eine Zeile id=2

Und jetzt Sitzung2

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

Sitzung 2 hängt sich auf, während versucht wird, die Zeilen-ID =2 zu sperren

OK, Sitzung 1 festschreiben

session1 #commit;
COMMIT
session1 #

und schauen Sie, was in Sitzung 2 passiert:

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

Bingo - Sitzung 2 hat die Zeilen-ID =2 übersprungen und die Zeilen-ID =3 ausgewählt (und gesperrt)


Unsere letzte Abfrage könnte also lauten:

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;

Einige Vorbehalte - dieses Beispiel dient nur zu Testzwecken und soll helfen zu verstehen, wie das Sperren in Postgre funktioniert.
Tatsächlich wird diese Abfrage den Zugriff auf die Tabelle serialisieren und ist nicht skalierbar und wird wahrscheinlich funktionieren schlecht (langsam) in Mehrbenutzerumgebung.
Stellen Sie sich vor, dass 10 Sitzungen gleichzeitig versuchen, die nächste Zeile aus dieser Tabelle zu erhalten - jede Sitzung wird hängen bleiben und warten, bis die vorherige Sitzung festgeschrieben wird.
Also nicht verwenden diese Abfrage im Produktionscode.
Möchten Sie wirklich "den nächsten Wert aus der Tabelle finden und reservieren" ? Warum ?
Falls ja, müssen Sie ein Serialisierungsgerät haben (wie diese Abfrage oder, vielleicht einfacher zu implementieren, das Sperren der gesamten Tabelle), aber das wird ein Engpass sein.