PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

postgresql - skrypt używający bloków transakcji nie tworzy wszystkich rekordów

Tak, robisz coś źle.
Spójrz na prosty przykład.

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


Sesja 2 - w tym samym czasie, ale tylko 10 ms później

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;

Sesja 2 zawiesza się ....... i czeka na coś ....

z powrotem w Sesji 1

postgres=# commit;
COMMIT
postgres=#



i ponownie Sesja 2

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


UPDATE 1
postgres=# commit;
COMMIT
postgres=#

Sesja 2 już nie czeka i kończy swoją transakcję.

A jaki jest efekt końcowy?:

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)

Dwóch użytkowników przyjęło tę samą wartość 1, ale tylko użytkownik 2 jest zarejestrowany w tabeli





======================EDYTUJ ==================================

W tym scenariuszu możemy użyć SELECT .. FOR UPDATE i wykorzystać sposób, w jaki postgre ponownie ocenia zapytanie w trybie Read Committed Isolation Level,
zobacz dokumentację:http://www.postgresql.org/docs/9.2/static/transaction-iso.html

W skrócie:
jeśli jedna sesja zablokowała wiersz, a druga próbuje zablokować ten sam wiersz, druga sesja „zawiesi się” i będzie czekać na zatwierdzenie lub wycofanie pierwszej sesji. zatwierdzi transakcję, wtedy druga sesja ponownie oceni warunek wyszukiwania WHERE. Jeśli warunek wyszukiwania nie jest zgodny (ponieważ pierwsza transakcja zmieniła niektóre kolumny), druga sesja pominie ten wiersz i przetworzy następny wiersz, który pasuje do WHERE warunki.

Uwaga:to zachowanie jest inne w przypadku powtarzalnych poziomów izolacji odczytu. W takim przypadku druga sesja wygeneruje błąd:nie można serializować dostępu z powodu współbieżnej aktualizacji i należy ponowić próbę całej transakcji.

Nasze zapytanie może wyglądać:

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

a następnie:

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



Osobiście wolę testować scenariusze blokowania używając zwykłych, starych klientów konsoli (psql dla postgree, mysql lub SQLPlus dla oracle)

Przetestujmy więc nasze zapytanie w 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 #

Sesja 1 zablokowała i zaktualizowała wiersz id=2

A teraz sesja2

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

Sesja 2 zawiesza się podczas próby zablokowania identyfikatora wiersza =2

OK, zatwierdźmy sesję 1

session1 #commit;
COMMIT
session1 #

i zobacz, co dzieje się w sesji 2:

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

Bingo - sesja 2 pominęła id wiersza =2 i zaznaczyła (i zablokowała) id wiersza =3


Więc nasze ostatnie zapytanie może wyglądać tak:

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;

Pewna rezerwacja — ten przykład służy tylko do celów testowych i ma na celu pomóc w zrozumieniu, jak działa blokowanie w postgre.
W rzeczywistości to zapytanie spowoduje serializację dostępu do tabeli i nie jest skalowalne i prawdopodobnie będzie działać źle (powolne) w środowisku wielu użytkowników.
Wyobraź sobie, że 10 sesji próbuje jednocześnie pobrać następny wiersz z tej tabeli - każda sesja zawiesi się i będzie czekała, aż poprzednia sesja zostanie zatwierdzona.
Więc nie używaj to zapytanie w kodzie produkcyjnym.
Czy na pewno chcesz "znaleźć i zarezerwować następną wartość z tabeli" ? Dlaczego?
Jeśli tak, musisz mieć jakieś urządzenie do serializacji (takie jak to zapytanie lub może łatwiejsze do zaimplementowania blokowanie całej tabeli), ale to będzie wąskie gardło.




  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Najlepsze materiały szkoleniowe i szkoleniowe dotyczące PostgreSQL

  2. jOOQ Znacznik czasu jest przechowywany z lokalnym przesunięciem strefy czasowej

  3. Laravel gdzie warunek - zapytanie pgsql

  4. Django:IntegrityError podczas add()

  5. Jak mogę utworzyć kolumnę w postgresie z wartości i selekcji opartych na innych kolumnach?