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?
fonte
Respostas:
rastrear deadlocks é o mais fácil dos dois:
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)
Bloco de código B (pseudocódigo)
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.
fonte
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.
fonte
À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 :
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.
fonte
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:
Se o seu campo de ID for escasso, convém extrair uma lista separada de IDs e iterar com isso:
fonte
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.
fonte
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.
fonte