Come interrogare e aumentare un valore (contatore) in modo thread-safe? (evitare le condizioni di gara)


10

In una tabella in cui ogni riga ha un contatore (solo un valore intero), devo ottenere il valore corrente e aumentarlo allo stesso tempo .

In effetti, voglio fare questo:

SELECT counter FROM table WHERE id=123
UPDATE table SET counter=counter+1 WHERE id=123

Ma fare questo in quanto due query ovviamente non è thread-safe: più processi che fanno la stessa cosa (sulla stessa riga) possono ottenere lo stesso valore del contatore. Ho bisogno che siano tutti univoci, quindi ogni processo otterrebbe il valore attuale effettivo e lo aumenterebbe di uno.

Posso pensare a una costruzione in cui implemento un blocco manuale per riga, ma mi chiedo se esiste un modo più semplice per farlo?


usando le transazioni forse?
ypercubeᵀᴹ

Risposte:


15

Le istruzioni di aggiornamento funzionano perfettamente senza prima selezionare! Poiché le singole istruzioni sono sicure per definizione, anche due query UPDATE eseguite contemporaneamente comporteranno un incremento della riga due volte.

Se in realtà vuoi selezionare il valore per il tuo script PHP, fare qualcosa con esso e successivamente vuoi aggiornare questo esatto valore del contatore, puoi fare quanto segue:

BEGIN;
SELECT `counter` FROM `table` WHERE `id` = 123 FOR UPDATE;
UPDATE `table` SET `counter` = `counter`+1 WHERE `id` = 123;
COMMIT;

Ciò avvia una nuova transazione, quindi seleziona le righe che si desidera aggiornare e le blocca esclusivamente. È quindi possibile aggiornare in modo sicuro quelli senza preoccuparsi che altri client cambino il loro contenuto o addirittura accedano alle file bloccate. Infine, devi eseguire il commit delle modifiche.

Dovresti anche leggere qualcosa sui livelli di isolamento . Probabilmente non vuoi un valore come READ UNCOMMITTEDlivello di isolamento. Tutto il resto dovrebbe andare bene per questo caso d'uso.


Ho letto in altri posti che UPDATE table SET counter = counter + 1è sufficientemente atomico? Hai ancora bisogno degli estratti conto che lo circondano?
CMCDragonkai,

@CMCDragonkai La tua query da sola è atomica, ma se selezioni il valore prima e non hai utilizzato FOR UPDATEe transazioni, il valore selezionato potrebbe essere diverso da quello utilizzato nella query di aggiornamento. La mia combinazione di query blocca la riga non appena viene selezionato il valore e quindi garantisce che questo valore esatto del contatore venga utilizzato nella query di aggiornamento.
GhostGambler

Ok, ma è necessario solo se faccio qualche lavoro diverso dall'incremento giusto? Allo stato attuale, una query di aggiornamento atomico solitario è abbastanza buona se è tutto ciò che voglio fare?
CMCDragonkai,

1
@CMCDragonkai Se non esegui un'altra query che tocca la colonna, sei a posto.
GhostGambler
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.