2014-04-23 11 views
6

Ho questa query che tenta di aggiungere righe alla tabella balances se non esiste una riga corrispondente nella tabella totals. La query viene eseguita in una transazione utilizzando il livello di isolamento predefinito su PostgreSQL.Una query INSERT-SELECT può essere soggetta a condizioni di competizione?

INSERT INTO balances (account_id, currency, amount) 
SELECT t.account_id, t.currency, 0 
FROM balances AS b 
RIGHT OUTER JOIN totals USING (account_id, currency) AS t 
WHERE b.id IS NULL 

Ho un vincolo UNIQUE su balances (accountId, currency). Sono preoccupato che entrerò in una situazione di condizione di competizione che porterà a duplicare gli errori delle chiavi se più sessioni eseguono questa query contemporaneamente. Ho visto molte domande su questo argomento, ma sembrano coinvolgere sia sottoquery, più query o funzioni pgSQL.

Poiché non sto utilizzando nessuno di quelli nella mia domanda, è libero da condizioni di gara? Se non è come posso ripararlo?

+0

Sì, è ancora possibile ottenere errori di chiave duplicati. Per lo stesso motivo puoi ottenerlo quando due sessioni eseguono la stessa istruzione 'insert' con la stessa clausola' values'. L'istruzione vede uno stato consistente del database mentre è in esecuzione, quindi non vedrà nessuna nuova riga anche se sono impegnate durante l'esecuzione dell'istruzione. –

+0

Un loop di funzione plpgsql in caso di violazione di una chiave duplicata può gestire la condizione di competizione sul lato server e al livello di isolamento predefinito, che è * sicuro * tipicamente * più economico *. L'app non deve preoccuparsi di tentativi: http://stackoverflow.com/questions/15939902/is-select-or-insert-in-a-function-prone-to-race-conditions/15950324#15950324 –

risposta

3

Si verrà a mancare con un errore duplicate key value violates unique constraint . Quello che faccio è inserire il codice di inserimento in un blocco try/except e quando viene lanciata l'eccezione, lo prendo e riprovo. Così semplice A meno che l'applicazione abbia una quantità enorme di utenti, funzionerà perfettamente.

Nella query è sufficiente il livello di isolamento predefinito poiché si tratta di una singola istruzione di inserimento e non vi è alcun rischio di letture fantasma.

Si noti che anche quando si imposta il livello di isolamento su serializzabile, il blocco try/except non è evitabile. From the manual about serializable:

come il livello Repeatable Read, le applicazioni che utilizzano questo livello deve essere preparato per riprovare le operazioni a causa di errori di serializzazione

+0

In realtà preferisco riprovare piuttosto che impostare il livello di transazione su serializzabile. Non dovrebbe essere un problema anche con molti utenti, perché l'insieme di righe che potrebbero mancare è limitato, quindi la maggior parte delle volte questa query dovrebbe essere un NOP. – LordOfThePigs

+0

@Lord Non è serializzabile contro riprova. Controlla l'aggiornamento. –

+0

Se devo riprovare comunque, preferirei non modificare il livello di isolamento della transazione. Vorrei poter accettare entrambe le risposte ... – LordOfThePigs

1

Il livello di transazione predefinito è Read Committed. Le letture fantasma sono possibili in questo livello (vedi Table 13.1). Mentre sei protetto dal vedere gli effetti strani nella tabella dei totali se dovessi aggiornare i totali, non sei protetto dalle letture fantasma nella tabella dei saldi.

Ciò significa che può essere spiegato quando si esamina una singola query simile alla propria che tenta il join esterno due volte (e solo le query, non inserisce nulla). Il fatto che manchi un bilanciamento non è garantito che rimanga lo stesso tra le due "sbirciatine" nella tabella delle bilance. L'improvvisa apparizione di un equilibrio che non era presente quando la stessa transazione è stata esaminata per la prima volta, viene definita "lettura fantasma".

Nel vostro caso, diverse istruzioni concorrenti possono vedere che manca un bilanciamento e nulla impedisce loro di provare ad inserirlo e ad uscire.

per escludere letture fantasma (e per fissare la query), è necessario eseguire nel nel livello di isolamento SERIALIZABLE prima di eseguire la query:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

+0

That's Ciò che ho pensato. Grazie per averlo confermato.Ho aggiunto una parte alla mia domanda: come posso risolvere la mia domanda? – LordOfThePigs

+1

Una singola istruzione non è soggetta a letture fantasma. Le letture fantasma possono avvenire solo se si eseguono due istruzioni una dopo l'altra in una singola transazione. Per una singola istruzione di inserimento, anche se basata su un'istruzione SELECT, le letture fantasma non possono avvenire (l'istruzione visualizza uno stato coerente del database mentre è in esecuzione). Quello che * può * accadere è che mentre l'inserto è in esecuzione altre transazioni stanno inserendo valori che risultano in una violazione PK ma che non è causata da una lettura fantasma. –

+0

@a_horse_with_no_name - Non conosco regole speciali per le singole istruzioni che si uniscono ripetutamente alla stessa tabella (possono essere compilate in piani di esecuzione arbitrari per quanto posso dire dalla documentazione). Sono d'accordo che questa domanda non riguarda direttamente uno scenario di lettura fantasma. Quello che sto dicendo è che il livello di transazione predefinito è insufficiente - non protegge la tabella delle bilance tramite un blocco di tabella. –

Problemi correlati