Le tue opzioni sono:
-
Esegui in
SERIALIZABLE
isolamento. Le transazioni interdipendenti verranno interrotte al momento del commit a causa di un errore di serializzazione. Riceverai un sacco di spam nel registro degli errori e farai molti tentativi, ma funzionerà in modo affidabile. -
Definisci un
UNIQUE
vincolo e riprova in caso di errore, come hai notato. Stessi problemi di cui sopra. -
Se è presente un oggetto padre, puoi
SELECT ... FOR UPDATE
l'oggetto padre prima di eseguire il tuomax
interrogazione. In questo caso dovrestiSELECT 1 FROM bar WHERE bar_id = $1 FOR UPDATE
. Stai usandobar
come lucchetto per tutti ifoo
s con quelbar_id
. Puoi quindi sapere che è sicuro procedere, a condizione che ogni query che esegue l'incremento del contatore lo faccia in modo affidabile. Questo può funzionare abbastanza bene.Questo esegue comunque una query aggregata per ogni chiamata, che (per opzione successiva) non è necessaria, ma almeno non invia spam al registro degli errori come le opzioni precedenti.
-
Usa un tavolo da banco. Questo è quello che farei. O in
bar
o in un tavolino comebar_foo_counter
, acquisisci un ID riga utilizzandoUPDATE bar_foo_counter SET counter = counter + 1 WHERE bar_id = $1 RETURNING counter
o l'opzione meno efficiente se il tuo framework non è in grado di gestire
RETURNING
:SELECT counter FROM bar_foo_counter WHERE bar_id = $1 FOR UPDATE; UPDATE bar_foo_counter SET counter = $1;
Quindi, nella stessa transazione , usa la riga del contatore generata per il
number
. Quando esegui il commit, la riga della tabella del contatore per quelbar_id
viene sbloccato per la query successiva da utilizzare. Se esegui il rollback, la modifica viene annullata.
Raccomando l'approccio del contatore, utilizzando una tabella laterale dedicata per il contatore invece di aggiungere una colonna a bar
. È più pulito da modellare e significa che crei meno aggiornamenti in bar
, che può rallentare le query su bar
.