Hoewel de suggestie van Erwin mogelijk de eenvoudigste is manier om correct gedrag te krijgen (zolang u uw transactie opnieuw probeert als u een uitzondering krijgt met SQLSTATE
van 40001) werken wachtrijapplicaties van nature beter met verzoeken die worden geblokkeerd voor een kans om hun beurt in de wachtrij te krijgen dan met de PostgreSQL-implementatie van SERIALIZABLE
transacties, wat een hogere gelijktijdigheid mogelijk maakt en iets "optimistischer" is over de kans op botsingen.
De voorbeeldquery in de vraag, zoals deze er nu uitziet, in de standaard READ COMMITTED
transactie-isolatieniveau zou twee (of meer) gelijktijdige verbindingen mogelijk maken om beide dezelfde rij uit de wachtrij te "claimen". Wat er zal gebeuren is dit:
- T1 start en komt zo ver als het vergrendelen van de rij in de
UPDATE
fase. - T2 overlapt T1 in uitvoeringstijd en probeert die rij bij te werken. Het blokkeert in afwachting van de
COMMIT
ofROLLBACK
van T1. - T1 commit, nadat hij de rij met succes heeft "geclaimd".
- T2 probeert de rij bij te werken, vindt die T1 al heeft, zoekt naar de nieuwe versie van de rij, vindt dat deze nog steeds voldoet aan de selectiecriteria (wat precies dat
id
is overeenkomsten) en "claimt" ook de rij.
Het kan worden aangepast om correct te werken (als u een versie van PostgreSQL gebruikt die de FOR UPDATE
toestaat clausule in een subquery). Voeg gewoon FOR UPDATE
. toe aan het einde van de subquery die de id selecteert, en dit zal gebeuren:
- T1 start en vergrendelt nu de rij voordat selecteert de id.
- T2 overlapt T1 in uitvoeringstijd en blokken tijdens het selecteren van een id, in afwachting van de
COMMIT
ofROLLBACK
van T1. - T1 commit, nadat hij de rij met succes heeft "geclaimd".
- Tegen de tijd dat T2 kan lezen de rij om de id te zien, ziet hij dat deze is geclaimd, dus vindt hij de volgende beschikbare id.
Bij de REPEATABLE READ
of SERIALIZABLE
transactie-isolatieniveau, zou het schrijfconflict een fout veroorzaken, die u zou kunnen opvangen en vaststellen dat het een serialisatiefout was op basis van de SQLSTATE, en het opnieuw proberen.
Als u over het algemeen SERIALISEERBARE transacties wilt, maar nieuwe pogingen in het wachtrijgebied wilt vermijden, kunt u dat mogelijk bereiken door een adviserende vergrendeling te gebruiken.