8

ho una stored procedure che esegue un join di TableB a TableA:Fix deadlock di SQL Server: forza l'ordine di join o riprovare automaticamente?

SELECT <--- Nested <--- TableA 
      Loop <-- 
         | 
         ---TableB 

Allo stesso tempo, in una transazione, righe vengono inserite TableA, e poi in TableB.

Questa situazione è a volte causando situazioni di stallo, in quanto la stored procedure di selezione afferra le righe da TableB, mentre l'inserto aggiunge righe da TableA, e poi ognuno vuole l'altro di lasciar andare l'altra tabella:

INSERT  SELECT 
========= ======== 
Lock A  Lock B 
Insert A Select B 
Want B  Want A 
....deadlock... 

Logic richiede l'INSERT aggiungere prima righe a, e poi a B, mentre io personalmente non mi interessa l'ordine in cui SQL Server esegue la sua iscriversi - finché si unisce.

La raccomandazione comune per la risoluzione dei deadlock è garantire che tutti accedano alle risorse nello stesso ordine. Ma in questo caso l'ottimizzatore di SQL Server mi sta dicendo che l'ordine opposto è "migliore". Posso forzare un altro ordine di join e avere una query con risultati peggiori.

Ma dovrei?

Devo eseguire l'override dell'ottimizzatore, ora e sempre, con un ordine di join che voglio utilizzarlo?

Oppure dovrei semplicemente intercettare l'errore nativo 1202 e inviare nuovamente l'istruzione select?

La domanda non è quanto peggio potrebbe essere la query quando eseguo l'override dell'ottimizzatore e perché faccia qualcosa di non ottimale. La domanda è: è meglio riprovare automaticamente, piuttosto che eseguire query peggiori?

risposta

9

È meglio riprovare automaticamente i deadlock. Il motivo è che è possibile risolvere questo deadlock, solo per colpirne un altro più tardi. Il comportamento può cambiare tra le versioni SQL, se la dimensione delle tabelle cambia, se le specifiche hardware del server cambiano e anche se il carico sul server cambia. Se il deadlock è frequente, è necessario adottare misure attive per eliminarlo (un indice è solitamente la risposta), ma per deadlock rari (diciamo ogni 10 minuti circa), riprovare nell'applicazione può mascherare il deadlock. Puoi riprovare a leggere le scritture o, poiché le scritture sono, ovviamente, circondate da una transazione di transazione/commit corretta per mantenere tutte le operazioni di scrittura atomiche e quindi in grado di riprovarle senza problemi.

Un'altra strada da prendere in considerazione è l'read committed snapshot. Quando è abilitato, SELECT semplicemente non prende alcun blocco, ma produce letture coerenti.

+0

Remus, riprovare letture ha perfettamente senso, ma riprovare automaticamente le scritture dopo deadlock porta a aggiornamenti persi. Quando scrivi e diventi una vittima del deadlock, è probabile che i dati che stai per toccare siano stati modificati da qualcun altro. Non dovremmo riscrivere automaticamente in questi casi. Dovremmo rileggere i dati eventualmente modificati e considerare nuovamente se vogliamo salvare. Ha senso? –

+1

@AlexKuznetsov: Questo è assurdo, se una transazione è scritta correttamente (cioè atomicamente), allora come potrebbe ritentare che potrebbe causare un aggiornamento perso? Sto dando questo +1, è sicuramente la risposta giusta. Non puoi fermare ogni deadlock, è solo una parte del rumore di fondo con la semantica ACID. – Aaronaught

+0

@Alex, @Aaro: in effetti hai entrambi ragione. Con "riprova" intendo davvero "leggi lo stato corrente, applica le modifiche, rispondi al nuovo stato". Per le applicazioni di elaborazione automatica, questo è un modello molto facile da ottenere. Tuttavia, per le applicazioni interattive degli utenti, questo può essere più difficile e spesso l'azione corretta consiste nel respingere la "scrittura" rileggendo lo stato corrente e re-visualizzarlo all'utente, in modo che lui/lei possa confermare che le modifiche applicate ha senso nel nuovo stato/contesto, e penso che questo fosse ciò che Alex aveva in mente. L'azione corretta dipende quindi da caso a caso. –

2

Trapping e rerunning possono funzionare, ma sei sicuro che SELECT sia sempre la vittima del deadlock? Se l'inserto è la vittima del deadlock, dovrai fare molta più attenzione a riprovare.

La soluzione più semplice in questo caso, penso, è NOLOCK o READUNCOMMITTED (stessa cosa) la selezione. Le persone hanno giustificate preoccupazioni riguardo alle letture sporche, ma abbiamo gestito NOLOCK dappertutto per una maggiore concorrenza per anni e non abbiamo mai avuto problemi.

Farei anche un po 'di ricerca sulla semantica dei blocchi. Ad esempio, credo che se si imposta il livello di isolamento della transazione su snapshot (richiesto nel 2005 o successivo) i problemi scompaiono.

+0

SQL Server esegue il rollback della transazione con il minor numero di risorse detenute. L '"inserto" è una serie transazionale di una dozzina di inserti. La selezione è una selezione solitaria (racchiusa in una stored procedure) –

+0

@Ian Boyd: una singola istruzione 'SELECT' non può creare una situazione di deadlock. È necessario disporre di almeno due transazioni multi-statement. Non hanno entrambi bisogno di essere DML, ma entrambi devono essere in attesa di blocchi sulle reciproche risorse e ciò significa che entrambi devono utilizzare almeno due risorse. Se è davvero solo una singola istruzione 'SELECT', non racchiusa in una transazione più grande, allora potrebbe non essere una vera situazione di stallo, potrebbe essere solo il sistema I/O che fatica a tenere il passo o qualche altro problema del server. – Aaronaught

+0

@'Aaronaught. Una singola selezione ** può ** causare un deadlock con un altro processo (http://blogs.msdn.com/bartd/archive/2006/09/25/770928.aspx) –

5

Per evitare deadlock, una delle raccomandazioni più comuni è "acquisire blocchi nello stesso ordine" o "accedere agli oggetti nello stesso ordine". Chiaramente questo ha perfettamente senso, ma è sempre fattibile? È sempre possibile? Continuo a incontrare casi in cui non posso seguire questo consiglio.

Se memorizzo un oggetto in una tabella padre e uno o più figli, non posso assolutamente seguire questo consiglio. Quando inserisco, devo inserire prima la mia riga genitore. Quando si elimina, devo farlo nell'ordine opposto.

Se utilizzo comandi che toccano più tabelle o più righe in una tabella, di solito non ho controllo su quali blocchi di ordine vengono acquisiti, (supponendo che non utilizzi i suggerimenti).

Quindi, in molti casi il tentativo di acquisire blocchi nello stesso ordine non impedisce tutti i deadlock. Quindi, abbiamo comunque bisogno di un po 'di gestione dei deadlock: non possiamo supporre che possiamo eliminarli tutti. A meno che, naturalmente, serializziamo tutti gli accessi utilizzando Service Broker o sp_getapplock.

Quando riproviamo dopo deadlock, è molto probabile che sovrascriviamo le modifiche di altri processi. Dobbiamo essere consapevoli del fatto che molto probabilmente qualcun altro ha modificato i dati che intendevamo modificare. Soprattutto se tutti i lettori corrono sotto l'isolamento dell'istantanea, quindi i lettori non possono essere coinvolti in deadlock, il che significa che tutte le parti coinvolte in un deadlock sono scrittori, modificati o tentato di modificare gli stessi dati. Se rileviamo l'eccezione e riproviamo automaticamente, possiamo sovrascrivere le modifiche di qualcun altro.

Questo è chiamato aggiornamenti persi, e questo di solito è sbagliato. In genere, la cosa giusta da fare dopo un deadlock è di riprovare a un livello molto più alto: ri-selezionare i dati e decidere se salvare nello stesso modo in cui è stata presa la decisione originale di salvare.

Ad esempio, se un utente ha premuto un pulsante Salva e la transazione di salvataggio è stata scelta come vittima del deadlock, potrebbe essere una buona idea visualizzare nuovamente i dati sullo schermo dopo il deadlock.

+0

+1 questo è vero nelle applicazioni interattive: se una scrittura aveva un deadlock, è molto probabile che lo stato che era stato aggiornato * fosse * cambiato, poiché questa è esattamente la risorsa in cui si è verificato il deadlock. La mia risposta è stata influenzata dal mio background nell'elaborazione della coda, in cui il "livello superiore" è contenuto nella transazione che viene ripristinata. –

+0

@AlexKuznetsov: non sono molto d'accordo sul rischio di riprovare un aggiornamento. Se l'utente è successo a fare clic sul pulsante 200ms più tardi, piuttosto che prima, l'effetto sarebbe stato lo stesso. –

+0

Se la tua applicazione è già progettata per supportare la concorrenza ottimistica, allora ha senso trattare un deadlock come conflitto. Se l'applicazione avrebbe comunque sovrascritto le modifiche, allora si potrebbe semplicemente riprovare l'aggiornamento. – Aaronaught