Quais são as principais causas de impasses e elas podem ser evitadas?

55

Recentemente, um de nossos aplicativos ASP.NET exibiu um erro de impasse no banco de dados e fui solicitado a verificar e corrigir o erro. Consegui descobrir que a causa do impasse era um procedimento armazenado que atualizava rigorosamente uma tabela dentro de um cursor.

Esta é a primeira vez que vi esse erro e não sabia como controlá-lo e corrigi-lo de forma eficaz. Tentei de todas as maneiras possíveis que conheço e finalmente descobri que a tabela que está sendo atualizada não possui uma chave primária! felizmente, era uma coluna de identidade.

Mais tarde, achei o desenvolvedor que criava scripts para o banco de dados para implantação. Eu adicionei uma chave primária e o problema foi resolvido.

Senti-me feliz e voltei ao meu projeto, e fiz algumas pesquisas para descobrir o motivo desse impasse ...

Aparentemente, foi uma condição de espera circular que causou o impasse. Aparentemente, as atualizações levam mais tempo sem uma chave primária do que com a chave primária.

Eu sei que não é uma conclusão bem definida, é por isso que estou postando aqui ...

  • A chave primária ausente é o problema?
  • Existem outras condições que causam impasse além de (exclusão mútua, espera e espera, sem preempção e espera circular)?
  • Como evito e acompanho os impasses?
CoderHawk
fonte
2
A maioria do IME (todos?) Dos conflitos que eu vi acontecer por causa de esperas circulares (principalmente por causa do uso excessivo de gatilhos).
precisa saber é o seguinte
A circularidade é uma das condições necessárias de um impasse. Você pode evitar qualquer impasse, se todas as suas sessões adquirirem bloqueios na mesma ordem.
Peter G.

Respostas:

38

rastrear deadlocks é o mais fácil dos dois:

Por padrão, os deadlocks não são gravados no log de erros. Você pode fazer com que o SQL grave deadlocks no log de erros com os sinalizadores de rastreamento 1204 e 3605.

Grave informações de conflito no log de erros do SQL Server: DBCC TRACEON (-1, 1204, 3605)

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

Consulte "Solução de problemas de deadlocks" para obter uma discussão sobre o sinalizador de rastreamento 1204 e a saída que você obterá quando estiver ligado. https://msdn.microsoft.com/en-us/library/ms178104.aspx

Prevenir é mais difícil, essencialmente você deve observar o seguinte:

O Bloco de Código 1 bloqueia o recurso A, depois o recurso B, nessa ordem.

O Bloco de Código 2 bloqueia o recurso B, depois o recurso A, nessa ordem.

Essa é a condição clássica em que um conflito pode ocorrer, se o bloqueio de ambos os recursos não for atômico, o Bloco de Código 1 poderá bloquear A e ser antecipado, então o Bloco de Código 2 trava B antes que A recupere o tempo de processamento. Agora você tem um impasse.

Para evitar essa condição, você pode fazer algo como o seguinte

Bloco de código A (código psuedo)

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

Bloco de código B (pseudocódigo)

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

não esquecendo de desbloquear A e B quando terminar com eles

isso impediria o conflito entre o bloco de código A e o bloco de código B

Do ponto de vista do banco de dados, não tenho certeza de como evitar essa situação, pois os bloqueios são tratados pelo próprio banco de dados, ou seja, bloqueios de linha / tabela ao atualizar os dados. Onde eu vi a maioria dos problemas ocorrer é onde você viu o seu, dentro de um cursor. Os cursores são notoriamente ineficientes, evite-os se possível.

Gelo preto
fonte
Você queria bloquear o recurso A antes do recurso B no Bloco de Código B? Como está escrito, isso causará impasses ... como você mencionou nos comentários anteriores. Na medida do possível, você deseja bloquear recursos na mesma ordem sempre, mesmo se precisar de consultas falsas no início para garantir essa ordem de bloqueio.
Gerard ONeill 5/03
23

meus artigos favoritos para ler e aprender sobre conflitos são: Conversa simples - Rastrear conflitos e o SQL Server Central - Usar o Profiler para resolver conflitos . Eles fornecerão amostras e conselhos sobre como lidar com uma situação de sucção.

Em resumo, para resolver um problema atual, eu reduzia as transações envolvidas, retira a parte desnecessária delas, cuido da ordem do uso dos objetos, vejo qual nível de isolamento é realmente necessário, e não lemos desnecessariamente dados...

Mas melhor ler os artigos, eles serão muito melhores em conselhos.

Marian
fonte
16

Às vezes, um conflito pode ser resolvido com a adição de indexação, pois permite que o banco de dados bloqueie registros individuais em vez de toda a tabela, reduzindo assim a contenção e a possibilidade de obstruções.

Por exemplo, no InnoDB :

Se você não possui índices adequados para sua instrução e o MySQL precisa varrer a tabela inteira para processar a instrução, todas as linhas da tabela ficam bloqueadas, o que, por sua vez, bloqueia todas as inserções de outros usuários na tabela. É importante criar bons índices para que suas consultas não varram desnecessariamente muitas linhas.

Outra solução comum é desativar a consistência transacional quando não for necessária ou alterar seu nível de isolamento , por exemplo, um trabalho de longa duração para calcular estatísticas ... uma resposta próxima geralmente é suficiente, você não precisa de números precisos, como eles estão mudando debaixo de você. E se levar 30 minutos para ser concluído, você não deseja que ele interrompa todas as outras transações nessas tabelas.

...

Quanto ao rastreamento, depende do software de banco de dados que você está usando.

Joe
fonte
É uma cortesia comum fornecer comentários quando a votação for reduzida ... Esta é uma resposta válida, uma instrução selecionada atualizando para um bloqueio de tabela e levando uma eternidade pode certamente causar um impasse.
BlackICE
11
O MS SQLServer também pode fornecer um comportamento inesperado de bloqueio se os índices não estiverem em cluster. Ele ignorará silenciosamente sua direção para usar o bloqueio no nível da linha e fará o bloqueio no nível da página. Você pode obter os impasses aguardando na página.
Jay
7

Apenas para desenvolver a coisa do cursor. é realmente muito ruim. Ele bloqueia a tabela inteira e processa as linhas uma a uma.

É melhor passar por linhas na forma de um cursor usando um loop while

No loop while, uma seleção será executada para cada linha do loop e o bloqueio ocorrerá em apenas uma linha por vez. O restante dos dados da tabela está livre para consulta, reduzindo assim as chances de um conflito.

Além disso, é mais rápido. Faz você se perguntar por que existem cursores de qualquer maneira.

Aqui está um exemplo desse tipo de estrutura:

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 o seu campo de ID for escasso, convém extrair uma lista separada de IDs e iterar com isso:

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
Nicolas de Fontenay
fonte
6

Faltar uma chave primária não é o problema. Pelo menos por si só. Primeiro, você não precisa de um primário para ter índices. Segundo, mesmo se você estiver fazendo varreduras de tabela (o que deve acontecer se sua consulta específica não estiver usando um índice, um bloqueio de tabela não causará um impasse por si só. Um processo de gravação aguardaria uma leitura e um processo de leitura faria. espere por uma gravação e, é claro, as leituras não precisariam esperar uma pela outra.

Adicionando às outras respostas, o nível de isolamento da transação é importante, porque a leitura repetida e a serialização são o que faz com que os bloqueios 'read' sejam mantidos até o final da transação. O bloqueio de um recurso não causa um conflito. Mantê-lo bloqueado faz. As operações de gravação sempre mantêm seus recursos bloqueados até o final da transação.

Minha estratégia favorita de prevenção de bloqueios é usar os recursos 'instantâneo'. O recurso Ler instantâneo confirmado significa que as leituras não usam bloqueios! E se você precisar de mais controle do que 'Leitura confirmada', há o recurso 'Nível de isolamento de captura instantânea'. Este permite que uma transação serializada (usando os termos da MS aqui) ocorra sem bloquear os outros players.

Por fim, uma classe de deadlocks pode ser evitada usando um bloqueio de atualização. Se você ler e reter a leitura (HOLD, ou usando Leitura Repetível) e outro processo fizer o mesmo, ambos tentarão atualizar os mesmos registros, você terá um impasse. Mas se os dois solicitarem um bloqueio de atualização, o segundo processo aguardará o primeiro, enquanto permitirá que outros processos leiam os dados usando bloqueios compartilhados até que os dados sejam realmente gravados. Obviamente, isso não funcionará se um dos processos ainda solicitar um bloqueio HOLD compartilhado.

Gerard ONeill
fonte
-2

Enquanto os cursores são lentos no SQL Server, você pode evitar o bloqueio de um cursor puxando os dados de origem do cursor para uma tabela Temp e executando o cursor nela. Isso evita que o cursor bloqueie a tabela de dados real e os únicos bloqueios que você obtém são as atualizações ou inserções realizadas dentro do cursor, que são mantidas apenas durante a inserção / atualização e não durante o cursor.

Bill Joyce
fonte