sql >> Databasteknik >  >> RDS >> PostgreSQL

postgresql - skript som använder transaktionsblock lyckas inte skapa alla poster

Ja, du gör något fel.
Titta på ett enkelt exempel.

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


Session 2 - samtidigt, men bara 10 ms senare

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;

Session 2 hänger sig ....... och väntar på något ....

tillbaka i Session 1

postgres=# commit;
COMMIT
postgres=#



och igen Session 2

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


UPDATE 1
postgres=# commit;
COMMIT
postgres=#

Session 2 väntar inte längre och avslutar sin transaktion.

Och vad är det slutliga resultatet?:

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)

Två användare tog samma värde 1, men endast användare 2 är registrerad i tabellen





======================REDIGERA ===================================

I det här scenariot kan vi använda SELECT .. FÖR UPPDATERING och använda ett sätt på vilket postgre omvärderar frågan i Read Committed Isolation Level-läge,
se dokumentationen:http://www.postgresql.org/docs/9.2/static/transaction-iso.html

Kort sagt:
om en session låste raden, och den andra sessionen försöker låsa samma rad, kommer den andra sessionen att "hänga sig" och väntar på att den första sessionen ska aktiveras eller återställas. När den första sessionen genomför transaktionen, då kommer den andra sessionen att omvärdera WHERE-sökvillkoret. Om sökvillkoret inte matchar (eftersom den första transaktionen ändrade några kolumner), kommer den andra sessionen att hoppa över den raden och bearbeta nästa rad som matchar WHERE villkor.

Obs:detta beteende är annorlunda i repeterbara läsisoleringsnivåer. I så fall kommer den andra sessionen att ge ett fel:kunde inte serialisera åtkomst på grund av samtidig uppdatering, och du måste försöka igen med hela transaktionen.

Vår fråga kan ser ut så här:

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

och sedan:

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



Personligen föredrar jag att testa låsscenarier med vanliga, gamla konsolklienter (psql för postgree, mysql eller SQLPlus för oracle)

Så låt testa vår fråga i 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 #

Session 1 låste och uppdaterade en rad id=2

Och nu session2

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

Session 2 hänger sig medan du försöker låsa rad-id =2

OK, låt commit session 1

session1 #commit;
COMMIT
session1 #

och se vad som händer i session 2:

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

Bingo - session 2 hoppade över rad-id =2 och valde (och låste) rad-id =3


Så vår sista fråga kan vara:

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;

Vissa reservationer - det här exemplet är endast för ditt testsyfte och syftet är att hjälpa till att förstå hur låsning fungerar i postgre.
Faktum är att den här frågan kommer att serialisera åtkomst till tabellen, och den är inte skalbar och kommer förmodligen att fungera dålig (långsam) i fleranvändarmiljö.
Föreställ dig att 10 sessioner samtidigt försöker få nästa rad från denna tabell - varje session kommer att hänga och väntar tills föregående session kommer att commit.
Så använd inte denna fråga i produktionskoden.
Vill du verkligen "hitta och reservera nästa värde från tabellen"? Varför?
Om ja, måste du ha någon serialiseringsenhet (som den här frågan, eller, kanske lättare att implementera, låsa hela tabellen), men detta kommer att vara en flaskhals.




  1. Unicode Encode Error 'latin-1' codec kan inte koda tecknet '\u2019'

  2. readyStatement-inställningen null för NUMBER_ARRAY fungerar inte

  3. Överför datatabell som parameter till lagrade procedurer

  4. MySQL:Vänster koppling och kolumn med samma namn i olika tabeller