Blocco in Postgres per la combinazione UPDATE / INSERT


11

Ho due tavoli. Uno è una tabella di registro; un altro contiene, essenzialmente, codici promozionali che possono essere utilizzati una sola volta.

L'utente deve essere in grado di riscattare un coupon, che inserirà una riga nella tabella di registro e contrassegnerà il coupon come utilizzato (aggiornando la usedcolonna in true).

Naturalmente, qui c'è un'evidente condizione di gara / problema di sicurezza.

Ho fatto cose simili in passato nel mondo di mySQL. In quel mondo, bloccherei entrambi i tavoli a livello globale, farei la logica sicura nella consapevolezza che ciò potrebbe accadere solo una volta alla volta, e quindi sbloccare i tavoli una volta che ho finito.

In Postgres c'è un modo migliore per farlo? In particolare, sono preoccupato che il blocco sia globale, ma non debba esserlo: devo solo assicurarmi che nessun altro stia cercando di inserire quel particolare codice, quindi forse un blocco a livello di riga funzionerebbe?

Risposte:


15

Ho sentito parlare di problemi di concorrenza come quello in MySQL prima. Non così in Postgres.

I blocchi integrati a livello di riga nel livello di READ COMMITTEDisolamento della transazione predefinito sono sufficienti.

Suggerisco una singola istruzione con un CTE che modifica i dati (qualcosa che anche MySQL non ha) perché è conveniente passare direttamente i valori da una tabella all'altra (se necessario). Se non hai bisogno di nulla dalla coupontabella, puoi utilizzare anche una transazione con istruzioni separate UPDATEe INSERT.

WITH upd AS (
   UPDATE coupon
   SET    used = true
   WHERE  coupon_id = 123
   AND    NOT used
   RETURNING coupon_id, other_column
   )
INSERT INTO log (coupon_id, other_column)
SELECT coupon_id, other_column FROM upd;

Dovrebbe essere raro che più di una transazione cerchi di riscattare lo stesso coupon. Hanno un numero univoco, no? Più di una transazione che prova nello stesso momento dovrebbe essere ancora più rara. (Forse un bug dell'applicazione o qualcuno sta cercando di giocare al sistema?)

Comunque sia, l' UPDATEunica riuscita ha esattamente una sola transazione, qualunque cosa accada. Un UPDATEacquisisce un blocco a livello di riga su ogni riga di destinazione prima dell'aggiornamento. Se una transazione simultanea tenta di eseguire UPDATEla stessa riga, vedrà il blocco sulla riga e attenderà il completamento ( ROLLBACKo COMMIT) della transazione di blocco , quindi sarà la prima nella coda di blocco:

  • Se eseguito, ricontrollare la condizione. Se è ancora NOT used, blocca la riga e procedi. Altrimenti UPDATEora non trova alcuna riga di qualificazione e non fa nulla , non restituisce alcuna riga, quindi INSERTanche non fa nulla.

  • Se ripristinato, bloccare la riga e procedere.

Non esiste alcun potenziale per una condizione di gara .

Non esiste alcun potenziale per un deadlock a meno che non si inseriscano più scritture nella stessa transazione o si blocchi in altro modo più righe di una sola.

Il INSERTè senza preoccupazioni. Se per qualche errore coupon_idè già presente nella logtabella (e hai un vincolo UNIQUE o PK attivato log.coupon_id), l'intera transazione verrà ripristinata dopo una violazione unica. Indicherebbe uno stato illegale nel tuo DB. Se l'istruzione precedente è l'unico modo per scrivere nella logtabella, ciò non dovrebbe mai accadere.


Dovrebbe in effetti essere una cosa rara che più di una transazione cerchi di riscattare lo stesso codice, ma i tuoi sospetti hanno ragione nel dire che ciò avverrà esclusivamente quando qualcuno sta cercando di giocare al sistema. Grazie mille per questo: i CTE sono stati una grande attrazione per me nel passaggio a Postgres, ma non mi rendevo conto che il blocco implicito sarebbe stato abbastanza buono per questo.
Rob Miller,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.