it-swarm.it

Quali sono le principali cause di deadlock e possono essere prevenute?

Recentemente una delle nostre applicazioni ASP.NET ha visualizzato un errore di deadlock del database e mi è stato chiesto di controllare e correggere l'errore. Sono riuscito a scoprire che la causa del deadlock era una procedura memorizzata che stava aggiornando rigorosamente una tabella all'interno di un cursore.

Questa è la prima volta che vedo questo errore e non sapevo come rintracciarlo e risolverlo in modo efficace. Ho provato tutti i modi possibili che conosco e alla fine ho scoperto che la tabella che viene aggiornata non ha una chiave primaria! per fortuna era una colonna di identità.

In seguito ho scoperto che lo sviluppatore che aveva copiato il database per la distribuzione era incasinato. Ho aggiunto una chiave primaria e il problema è stato risolto.

Mi sono sentito felice e sono tornato al mio progetto e ho fatto qualche ricerca per scoprire il motivo di quel punto morto ...

Apparentemente, era una condizione di attesa circolare che ha causato lo stallo. Apparentemente gli aggiornamenti impiegano più tempo senza una chiave primaria che con la chiave primaria.

So che non è una conclusione ben definita, ecco perché sto postando qui ...

  • La chiave primaria mancante è il problema?
  • Esistono altre condizioni che causano deadlock diversi da (esclusione reciproca, attesa e attesa, nessuna prelazione e attesa circolare)?
  • Come posso prevenire e tenere traccia dei deadlock?
57
CoderHawk

il monitoraggio dei deadlock è il più semplice dei due:

Per impostazione predefinita, i deadlock non sono scritti nel registro degli errori. È possibile fare in modo che SQL scriva deadlock nel registro errori con i flag di traccia 1204 e 3605.

Scrivere informazioni di deadlock nel registro errori di SQL Server: DBCC TRACEON (-1, 1204, 3605)

Disattiva: DBCC TRACEOFF (-1, 1204, 3605)

Vedere "Risoluzione dei problemi dei deadlock" per una discussione del flag di traccia 1204 e dell'output che si otterrà quando è acceso. https://msdn.Microsoft.com/en-us/library/ms178104.aspx

La prevenzione è più difficile, in sostanza devi cercare quanto segue:

Il blocco di codice 1 blocca la risorsa A, quindi la risorsa B, in questo ordine.

Il blocco di codice 2 blocca la risorsa B, quindi la risorsa A, in questo ordine.

Questa è la classica condizione in cui può verificarsi un deadlock, se il blocco di entrambe le risorse non è atomico, il blocco di codice 1 può bloccare A ed essere prevenuto, quindi il blocco di codice 2 blocca B prima che A ottenga indietro il tempo di elaborazione. Ora hai deadlock.

Per prevenire questa condizione, puoi fare qualcosa di simile al seguente

Code Block A (codice psuedo)

Lock Shared Resource Z
    Lock Resource A
    Lock Resource B
Unlock Shared Resource Z
...

Code Block B (codice pseudo)

Lock Shared Resource Z
    Lock Resource B
    Lock Resource A
Unlock Shared Resource Z
...

senza dimenticare di sbloccare A e B quando hai finito con loro

questo impedirebbe il deadlock tra il blocco di codice A e il blocco di codice B

Dal punto di vista del database, non sono sicuro su come evitare questa situazione, poiché i blocchi sono gestiti dal database stesso, vale a dire i blocchi riga/tabella durante l'aggiornamento dei dati. Dove ho visto che si verificano più problemi è dove hai visto i tuoi, all'interno di un cursore. I cursori sono notoriamente inefficienti, evitali se possibile.

39
BlackICE

i miei articoli preferiti da leggere e conoscere i deadlock sono: Simple Talk - Rintraccia deadlock e SQL Server Central - Utilizzo di Profiler per risolvere i deadlock . Ti daranno esempi e consigli su come gestire la situazione.

In breve, per risolvere un problema attuale, renderei più brevi le transazioni coinvolte, eliminerei la parte non necessaria da esse, mi occuperei dell'ordine dell'uso degli oggetti, vedevo quale livello di isolamento è effettivamente necessario, non leggevo non necessario dati...

Ma meglio leggere gli articoli, saranno molto più gentili nei consigli.

24
Marian

A volte un deadlock può essere risolto aggiungendo l'indicizzazione, in quanto consente al database di bloccare singoli record anziché l'intera tabella, in modo da ridurre la contesa e la possibilità che le cose si incastrino.

Ad esempio, in InnoDB :

Se non si dispone di indici adatti all'istruzione e MySQL deve eseguire la scansione dell'intera tabella per elaborare l'istruzione, ogni riga della tabella viene bloccata, il che a sua volta blocca tutti gli inserimenti degli altri utenti nella tabella. È importante creare buoni indici in modo che le query non eseguano inutilmente la scansione di molte righe.

Un'altra soluzione comune è disattivare la coerenza transazionale quando non è necessaria o modificare in altro modo livello di isolamento , ad esempio, un lavoro di lunga durata per calcolare le statistiche ... una risposta ravvicinata è generalmente sufficiente, tu non hanno bisogno di numeri precisi, poiché cambiano da sotto di te. E se ci vogliono 30 minuti per completare, non vuoi che interrompa tutte le altre transazioni su quei tavoli.

...

Per quanto riguarda il loro monitoraggio, dipende dal software del database che stai utilizzando.

16
Joe

Solo per sviluppare la cosa del cursore. è davvero davvero male. Blocca l'intera tabella quindi elabora le righe una per una.

È meglio passare attraverso le righe come un cursore usando un ciclo while

Nel ciclo while, verrà eseguita una selezione per ogni riga del ciclo e il blocco avverrà su una sola riga alla volta. Il resto dei dati nella tabella è gratuito per le query, riducendo così le possibilità che si verifichi un deadlock.

Inoltre è più veloce. Ti fa chiedere perché ci sono comunque cursori.

Ecco un esempio di questo tipo di struttura:

DECLARE @LastID INT = (SELECT MAX(ID) FROM Tbl)
DECLARE @ID     INT = (SELECT MIN(ID) FROM Tbl)
WHILE @ID <= @LastID
    BEGIN
    IF EXISTS (SELECT * FROM Tbl WHERE ID = @ID)
        BEGIN
        -- Do something to this row of the table
        END

    SET @ID += 1  -- Don't forget this part!
    END

Se il tuo campo ID è scarso, potresti voler estrarre un elenco separato di ID e scorrere attraverso quello:

DECLARE @IDs TABLE
    (
    Seq INT NOT NULL IDENTITY PRIMARY KEY,
    ID  INT NOT NULL
    )
INSERT INTO @IDs (ID)
    SELECT ID
    FROM Tbl
    WHERE 1=1  -- Criteria here

DECLARE @Rec     INT = 1
DECLARE @NumRecs INT = (SELECT MAX(Seq) FROM @IDs)
DECLARE @ID      INT
WHILE @Rec <= @NumRecs
    BEGIN
    SET @ID = (SELECT ID FROM @IDs WHERE Seq = @Seq)

    -- Do something to this row of the table

    SET @Seq += 1  -- Don't forget this part!
    END
7

Manca una chiave primaria è not il problema. Almeno da solo. Innanzitutto, non è necessario un primario per avere indici. In secondo luogo, anche se stai eseguendo scansioni di tabelle (cosa che deve accadere se la tua query specifica non utilizza un indice, un blocco delle tabelle non causerà da solo un deadlock. Un processo di scrittura aspetterebbe una lettura e un processo di lettura aspettare una scrittura, e ovviamente le letture non dovrebbero aspettarsi affatto.

Aggiungendo alle altre risposte, il livello di isolamento della transazione è importante, perché la lettura ripetibile e la serializzazione sono ciò che causa la conservazione dei blocchi di "lettura" fino alla fine della transazione. Il blocco di una risorsa non provoca un deadlock. Tenendolo bloccato fa. Le operazioni di scrittura mantengono sempre bloccate le risorse fino alla fine della transazione.

La mia strategia preferita per la prevenzione dei blocchi sta usando le funzionalità 'istantanea'. La funzione Leggi istantanea impegnata significa che le letture non usano i blocchi! E se hai bisogno di un controllo maggiore rispetto a "Leggi impegnati", c'è la funzione "Livello di isolamento dello scatto istantaneo". Questo permette una transazione serializzata (usando qui i termini MS) senza bloccare gli altri giocatori.

Infine, è possibile prevenire una classe di deadlock utilizzando un blocco degli aggiornamenti. Se leggi e tieni premuta la lettura (HOLD, o usando la lettura ripetibile), e un altro processo fa lo stesso, allora entrambi provano ad aggiornare gli stessi record, avrai un deadlock. Ma se entrambi richiedono un blocco degli aggiornamenti, il secondo processo attenderà il primo, consentendo ad altri processi di leggere i dati utilizzando i blocchi condivisi fino a quando i dati non vengono effettivamente scritti. Questo ovviamente non funzionerà se uno dei processi richiede ancora un blocco HOLD condiviso.

6
Gerard ONeill