2010-09-14 13 views
7

Quando si esegue un'istruzione SELECT con un JOIN di due tabelle, SQL Server sembra che blocchi singolarmente entrambe le tabelle dell'istruzione. Ad esempio una query come questo:Deadlock causato dall'istruzione SELECT JOIN con SQL Server

SELECT ... 
FROM 
    table1 
    LEFT JOIN table2 
     ON table1.id = table2.id 
    WHERE ... 

ho scoperto che l'ordine dei blocchi dipende dalle condizioni WHERE. L'ottimizzatore di query tenta di produrre un piano di esecuzione che legge solo le righe necessarie. Quindi, se la condizione WHERE contiene una colonna della tabella1 , per prima cosa otterrà le righe dei risultati da table1 e quindi otterrà le corrispondenti righe da table2. Se la colonna proviene da table2 lo farà nell'altro modo round. Condizioni più complesse o l'uso di indici possono avere un effetto su anche la decisione di Query Optimizer.

Quando i dati letti da una dichiarazione devono essere aggiornati successivamente nella transazione con le istruzioni UPDATE non è garantito che l'ordine delle UPDATE dichiarazioni corrisponde l'ordine che è stato utilizzato per leggere i dati dalle tabelle 2. Se un'altra transazione tenta di leggere i dati mentre una transazione sta aggiornando le tabelle , può causare un deadlock quando l'istruzione SELECT viene eseguita in tra le istruzioni UPDATE perché né SELECT può ottenere il blocco su la prima tabella né l'UPDATE ottenere il blocco sul secondo tavolo. Ad esempio, :

T1: SELECT ... FROM ... JOIN ... 
T1: UPDATE table1 SET ... WHERE id = ? 
T2: SELECT ... FROM ... JOIN ... (locks table2, then blocked by lock on table1) 
T1: UPDATE table2 SET ... WHERE id = ? 

Entrambe le tabelle rappresentano una gerarchia di tipo e sono sempre caricati in comune. Quindi è che ha senso caricare un oggetto usando un SELECT con un JOIN. Il caricamento di entrambe le tabelle individualmente non consentirebbe a Query Optimizer di trovare il miglior piano di esecuzione . Ma dal momento che le istruzioni UPDATE possono aggiornare una tabella solo in un'ora , questo può causare deadlock quando viene caricato un oggetto mentre l'oggetto viene aggiornato da un'altra transazione. Gli aggiornamenti degli oggetti spesso causano AGGIORNAMENTI su entrambe le tabelle quando vengono aggiornate le proprietà dell'oggetto che appartengono a tipi diversi della gerarchia di tipi .

Ho provato ad aggiungere suggerimenti di blocco all'istruzione SELECT, ma ciò non risolve il problema con . Causa solo il deadlock nelle istruzioni SELECT quando entrambe le istruzioni tentano di bloccare le tabelle e un'istruzione SELECT ottiene il blocco nell'ordine opposto dell'altra istruzione. Forse sarebbe possibile per caricare i dati per gli aggiornamenti sempre con la stessa istruzione forzando i blocchi a essere nello stesso ordine. Ciò impedirebbe un deadlock tra due transazioni che desidera aggiornare i dati, ma non impedirebbe una transazione che legge solo i dati deadlock che deve avere condizioni WHERE diverse.

L'unica work-a-round quindi sembra che le letture potrebbero non avere lucchetti . Con SQL Server 2005 questo può essere fatto usando SNAPSHOT ISOLATION. L'unico modo per SQL Server 2000 per l' è l'utilizzo del livello di isolamento READ UNCOMMITED .

Mi piacerebbe sapere se esiste un'altra possibilità per impedire a SQL Server di provocare questi deadlock?

+0

livello di isolamento dello snapshot? –

+0

Se questa è una domanda la risposta è no. Succede con tutti i livelli di isolamento, tranne forse LEGGI NON CORRETTO che non ho provato, perché non voglio che le transazioni possano leggere dati semestrali. – Reboot

risposta

5

Questo non avverrà mai con l'isolamento dell'istantanea, quando i lettori non bloccano i writer. Oltre a questo, non c'è modo di impedire cose del genere. Ho scritto un sacco di script Repro qui: Reproducing deadlocks involving only one table

Edit:

io non ho accesso a SQL 2000, ma vorrei cercare di serializzare l'accesso all'oggetto utilizzando sp_getapplock, in modo che la lettura e la modifica mai correre contemporaneamente. Se non puoi usare sp_getapplock, usa il tuo mutex.

+0

+1 per la ricerca deadlock –

+0

La risposta mi ha aiutato a trovare una soluzione con SQL Server 2005 o versione successiva. Ma il software è ancora usato con SQL Server 2000, posso probabilmente implementare diverse soluzioni per entrambe le versioni, quindi una soluzione che funzioni per tutte le versioni o una soluzione diversa per SQL Server 2000 sarebbe apprezzata. – Reboot

-1

Mi trovavo di fronte allo stesso problema. Utilizzando il suggerimento di query FORCE ORDER risolverà questo problema. Il rovescio della medaglia è che non sarai in grado di sfruttare il miglior piano che ha Query Optimizer per la tua query, ma ciò impedirà il deadlock.

Quindi (questo è dell'utente "Bill the Lizard") se si ha una query FROM table1 LEFT JOIN table2 e la clausola WHERE contiene solo colonne da table2 il piano di esecuzione in genere seleziona prima le righe da table2 e quindi cerca le righe da table1. Con un piccolo set di risultati di table2, è necessario recuperare solo poche righe da table1. Con FORCE ORDER prima tutte le righe da table1 devono essere recuperate, perché non ha alcuna clausola WHERE, quindi le righe di table2 vengono unite e il risultato viene filtrato usando la clausola WHERE. Quindi prestazioni degradanti.

Ma se sapete che questo non sarà il caso, usate questo. Potresti voler ottimizzare la query manualmente.

La sintassi è

SELECT ... 
FROM 
    table1 
    LEFT JOIN table2 
     ON table1.id = table2.id 
    WHERE ... 
OPTION (FORCE ORDER) 
+0

In realtà ho scritto il commento sulla performance, sembra Stack Overflow ha incasinato la tua risposta e il mio commento, mi sono già chiesto perché è scomparso. Il problema non è solo la performance con FORCE ORDER, è anche il blocco. Se si utilizza FORCE ORDER e il piano di esecuzione causa la lettura di tutte le righe da table1, viene inserito anche un blocco condiviso. Quindi, fondamentalmente ogni query che utilizza solo le colonne della tabella2 bloccherà l'intera tabella1 rendendo impossibile qualsiasi aggiornamento. Non è una singola query che deve funzionare, la SELECT deve funzionare con qualsiasi clausola WHERE. – Reboot

+0

Quindi non vuoi bloccare table1, ma puoi unirti a table2? Nel mio caso, ho avuto inserimenti che si verificano in Table1 e quindi in Table2.E Query Optimizer stava selezionando l'ordine inverso in caso di join. Quindi deadlock. L'aggiunta di ORDINE DI FORZA ha risolto il problema, quindi nessun deadlock. Nota qui voglio ancora leggere il blocco su table1. Gli inserti possono attendere fino a quando la selezione finisce. Ma non ci sarà nessun punto morto. – Ankush

+0

Nessun blocco è ok su table1, ma con FORCE ORDER blocca tutte le righe nella tabella anziché solo le righe che devono essere unite alle righe da table2. Anche l'allocazione dei blocchi per tutte le righe può diventare un problema, poiché è necessario allocare i processi di una risorsa. Se SQL Server esegue la nostra memoria per i blocchi, i processi possono bloccarsi sull'allocazione della memoria. – Reboot

0

Un altro modo per risolvere questo problema è quello di dividere il select ... da ... unirsi in più istruzioni SELECT. Imposta il livello di isolamento su read committed. Utilizza la variabile table per convogliare i dati da select per essere uniti ad altri. Usa distinto per filtrare gli inserti in queste variabili di tabella.

Quindi se ho due tabelle A, B. Sto inserendo/aggiornando in A e poi B. Dove come lo sql's query optimizer preferisce leggere prima B e A. Io dividerò la singola selezione in 2 seleziona. Innanzitutto leggo B. Quindi trasmetti questi dati alla successiva istruzione select che legge A.

Qui il deadlock non si verifica perché i blocchi di lettura sulla tabella B verranno rilasciati non appena viene eseguita la prima istruzione.

PS Ho affrontato questo problema e questo ha funzionato molto bene. Molto meglio della mia risposta dell'ordine di forza.