2010-06-24 13 views
6

Ho un problema che la stessa stored procedure viene invocata esattamente nello stesso momento con esattamente gli stessi paramenter.Inserisci problema concorrenza - Ambiente con multithreading

Lo scopo della procedura memorizzata è di recuperare un record se esiste o di creare e recuperare il record se non esiste.

Il problema è che entrambi i thread verificano l'esistenza del record e segnalano false e quindi entrambi stanno inserendo un nuovo record, creando un duplicato nel database.

Ho provato ad attaccare le operazioni all'interno di una transazione, ma questo ha prodotto solo centinaia di deadlock.

Esiste un modo per controllare l'esistenza del record in modo thread-safe in modo che il secondo thread non esegua la lettura finché il primo non ha terminato l'inserimento? Non ho alcun controllo sui thread stessi, solo sui processi memorizzati che stanno eseguendo.

Qualsiasi aiuto sarebbe gradito,

Grazie.

+0

Ha prodotto deadlock, cosa stanno facendo esattamente queste transazioni; non devono essere semplici come affermate. Che tipo di RDBMS stai usando? – BobbyShaftoe

+0

Al contrario, la procedura è molto semplice: controlla se il record esiste già, lo crea se non lo fa e quindi seleziona il record. L'unico motivo per cui abbiamo un problema è che ci sono molti thread concorrenti (fino a 200). – DJCasey

+0

Oh, ho dimenticato di dire - utilizzando SQL Server 2008. – DJCasey

risposta

0

Si è verificato un problema durante l'esecuzione di una selezione e quindi l'inserimento, quindi normalmente c'è un blocco di lettura per la selezione e quindi un blocco di scrittura sull'inserto. Senza una transazione, la tempistica di molti aggiornamenti consentirà spesso l'inserimento di più inserti come si vede. In una transazione il primo blocco di lettura bloccherà altri processi che ottengono un blocco di scrittura e se più di un processo ottiene un blocco di lettura, allora nessuno può ottenere un blocco di scrittura e quindi si ottiene un deadlock.

In questo caso modificherei il codice dell'inserto in modo che gli indici consentano solo un inserto di funzionare, ovvero che tu abbia una chiave univoca e solo un processo non sarà in grado di inserire i dati in modo da non ottenere duplicati. il processo di aggiornamento è quindi in una transazione sia

1) fare l'inserto prima e trattare con un'eccezione o un errore se si tenta di inserire un duplicato

o 2) fare un blocco HOLD (Sybase e SQL Server) prima di selezionare la selezione - quindi il primo a bloccare ottiene il permesso completo di inserire se necessario

o 3) Utilizzare eventualmente il comando di unione se RDBMS lo consente. Questo controlla e inserisce tutto in un comando, ma cambierà sempre il database.

MODIFICA: Penso che non ci sia una vera alternativa a 1 se è necessario assicurarsi che ci sia uno e un solo record inserito come test per che deve essere in una transazione.

il costo può essere ridotto verificando la presenza di esistenza in una transazione e quindi facendo l'inserto e registrando un'altra transazione. Quindi nella maggior parte dei casi hai solo una selezione e negli altri casi ottieni l'inserto lento e controlla ma questo dovrebbe accadere meno spesso.

+0

Avevo già provato la prima soluzione, ma ha rallentato troppo il processo - ho anche provato un blocco di blocco, ma questo non risolve il problema poiché entrambi i thread hanno già un blocco condiviso sul tavolo e nessuno dei due può elevare. Non sono sicuro del comando di unione, ma se modifica sempre il database, allora non è quello che voglio. – DJCasey

0

Non sicuro se SQL Server ce l'ha. Ma in MySQL e in Oracle è possibile ottenere il blocco della scrittura mentre si effettua una selezione utilizzando per la sintassi di aggiornamento.

select * 
from table 
for update 

Poiché anche altri thread richiedono il blocco della scrittura mentre si seleziona, attenderanno fino al completamento del primo thread della transazione.

+0

Ha lo stesso effetto di una transazione: crea blocchi sul tavolo. Questa particolare sintassi, senza istruzioni where, bloccherà effettivamente l'intera tabella. SQL Server non ha questa sintassi ma anche con Oracle questa sintassi comporta ritardi rispetto alle versioni più semplici. –

7

Il trucco è aggiungere un WHERE alla propria istruzione INSERT in modo che INSERT funzioni solo se l'elemento non esiste, seguito dall'istruzione SELECT. Supponendo che il record possa essere identificato da una colonna ID si può scrivere:

INSERT INTO MyTable (ID,Col1,Col2,...) 
SELECT @IDValue,@Col1Value,@Col2Value, ... 
WHERE NOT EXISTS (SELECT ID 
       FROM MyTable 
       WHERE [email protected]) 

SELECT * 
FROM MyTable 
Where [email protected] 

Non è necessario mettere le dichiarazioni in una transazione, perché ogni istruzione viene eseguita nella propria transazione implicita. Quindi, non c'è modo che due INSERTI riusciranno nello stesso tempo.

EDIT: la sintassi INSERT ... SELECT è necessaria perché TSQL non consente una parte VALUE e WHERE nell'istruzione INSERT.

+0

Mi chiedo se è necessario aggiungere WITH (UPDLOCK, HOLDLOCK) all'istruzione select interna? –

+0

Qualunque cosa? L'operazione è atomica. HOLDLOCK manterrà i blocchi esattamente nello stesso modo in cui avverrà una transazione, fino a quando la connessione non si chiude o la transazione che lo circonda (se ce ne fosse una) completa. In effetti, HOLDLOCK è peggio di una transazione esplicita perché non è più possibile controllare i blocchi fino alla chiusura della connessione –

0

Suppongo che stiate utilizzando C# per comunicare con il server sql, quindi potete provare a cercare nel parallelismo delle attività e nella libreria delle attività per il multithreading delle stored procedure.

+0

È altamente improbabile che la modifica della tecnica per l'accesso a una risorsa esterna possa risolvere un problema di concorrenza. Potrebbe anche non essere valido se lo fosse, dal momento che non siamo sicuri di quale sia il client. –

Problemi correlati