Mentre il suggerimento di Erwin è forse il più semplice modo per ottenere un comportamento corretto (a condizione che tu riprovi la transazione se ottieni un'eccezione con SQLSTATE
di 40001), le applicazioni di accodamento per loro natura tendono a funzionare meglio con il blocco delle richieste per avere la possibilità di fare il loro turno in coda rispetto all'implementazione PostgreSQL di SERIALIZABLE
transazioni, che consente una maggiore concorrenza ed è un po' più "ottimista" sulle possibilità di collisione.
La query di esempio nella domanda, così com'è, nel valore predefinito READ COMMITTED
il livello di isolamento della transazione consentirebbe a due (o più) connessioni simultanee di "rivendicare" la stessa riga dalla coda. Quello che accadrà è questo:
- T1 inizia e arriva fino al blocco della riga in
UPDATE
fase. - T2 si sovrappone a T1 nel tempo di esecuzione e tenta di aggiornare quella riga. Si blocca in attesa del
COMMIT
oROLLBACK
di T1. - T1 esegue il commit, dopo aver "rivendicato" con successo la riga.
- T2 prova ad aggiornare la riga, trova che T1 ha già, cerca la nuova versione della riga, trova che soddisfa ancora i criteri di selezione (che è proprio quell'
id
corrispondenze) e anche "rivendica" la riga.
Può essere modificato per funzionare correttamente (se stai usando una versione di PostgreSQL che consente il FOR UPDATE
clausola in una sottoquery). Basta aggiungere FOR UPDATE
alla fine della sottoquery che seleziona l'id, e questo accadrà:
- T1 si avvia e ora blocca la riga prima di selezionare l'id.
- T2 si sovrappone a T1 nel tempo di esecuzione e si blocca durante il tentativo di selezionare un id, in attesa del
COMMIT
oROLLBACK
di T1. - T1 esegue il commit, dopo aver "rivendicato" con successo la riga.
- Quando T2 sarà in grado di leggere la riga per vedere l'id, vede che è stato rivendicato, quindi trova il successivo ID disponibile.
Alla REPEATABLE READ
o SERIALIZABLE
livello di isolamento della transazione, il conflitto di scrittura genererebbe un errore, che potresti rilevare e determinare un errore di serializzazione basato su SQLSTATE e riprovare.
Se in genere desideri transazioni SERIALIZZABILI ma desideri evitare nuovi tentativi nell'area di coda, potresti riuscire a farlo utilizzando un blocco di avviso.