Eu tenho uma tabela innoDB que registra usuários online. Ele é atualizado a cada atualização de página por um usuário para acompanhar quais páginas estão e sua última data de acesso ao site. Em seguida, tenho um cron que é executado a cada 15 minutos para excluir registros antigos.
Eu encontrei um 'Deadlock encontrado ao tentar trancar; tente reiniciar a transação 'por cerca de 5 minutos na noite passada e parece estar executando os INSERTs nesta tabela. Alguém pode sugerir como evitar esse erro?
=== EDIT ===
Aqui estão as consultas que estão sendo executadas:
Primeira visita ao site:
INSERT INTO onlineusers SET
ip = 123.456.789.123,
datetime = now(),
userid = 321,
page = '/thispage',
area = 'thisarea',
type = 3
Em cada atualização de página:
UPDATE onlineusers SET
ips = 123.456.789.123,
datetime = now(),
userid = 321,
page = '/thispage',
area = 'thisarea',
type = 3
WHERE id = 888
Cron a cada 15 minutos:
DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND
Em seguida, faz algumas considerações para registrar algumas estatísticas (ou seja, membros online, visitantes online).
Respostas:
Um truque fácil que pode ajudar com a maioria dos impasses é classificar as operações em uma ordem específica.
Você obtém um impasse quando duas transações estão tentando bloquear dois bloqueios em ordens opostas, ou seja:
Se os dois rodarem ao mesmo tempo, a conexão 1 bloqueará a chave (1), a conexão 2 bloqueará a chave (2) e cada conexão aguardará que a outra libere a tecla -> deadlock.
Agora, se você alterou suas consultas para que as conexões travassem as chaves na mesma ordem, ou seja:
será impossível obter um impasse.
Então é isso que eu sugiro:
Verifique se não há outras consultas que bloqueiam o acesso a mais de uma chave por vez, exceto a instrução delete. se você fizer (e eu suspeito que sim), encomende o WHERE em (k1, k2, .. kn) em ordem crescente.
Corrija sua instrução de exclusão para funcionar em ordem crescente:
mudança
Para
Outra coisa a ter em mente é que a documentação do mysql sugere que, no caso de um conflito, o cliente deve tentar novamente automaticamente. você pode adicionar essa lógica ao código do seu cliente. (Digamos, 3 tentativas nesse erro específico antes de desistir).
fonte
O conflito ocorre quando duas transações se esperam para adquirir um bloqueio. Exemplo:
Existem inúmeras perguntas e respostas sobre conflitos. Cada vez que você insere / atualiza ou exclui uma linha, um bloqueio é adquirido. Para evitar deadlock, você deve garantir que as transações simultâneas não atualizem a linha em uma ordem que possa resultar em um deadlock. De um modo geral, tente adquirir o bloqueio sempre na mesma ordem, mesmo em transações diferentes (por exemplo, sempre a tabela A primeiro e depois a tabela B).
Outro motivo para o conflito no banco de dados pode estar faltando índices . Quando uma linha é inserida / atualizada / excluída, o banco de dados precisa verificar as restrições relacionais, ou seja, verifique se as relações são consistentes. Para fazer isso, o banco de dados precisa verificar as chaves estrangeiras nas tabelas relacionadas. Isso pode resultar na aquisição de outro bloqueio que não seja a linha modificada. Certifique-se de sempre ter índice nas chaves estrangeiras (e, é claro, nas chaves primárias), caso contrário, isso poderá resultar em um bloqueio de tabela em vez de um bloqueio de linha . Se o bloqueio da tabela ocorrer, a contenção do bloqueio é maior e a probabilidade de conflito aumenta.
fonte
É provável que a instrução de exclusão afete uma grande fração do total de linhas na tabela. Eventualmente, isso pode levar à aquisição de um bloqueio de tabela ao excluir. Manter um bloqueio (nesse caso, bloqueios de linha ou de página) e adquirir mais bloqueios é sempre um risco de conflito. No entanto, não posso explicar por que a instrução insert leva a uma escalada de bloqueio - pode ter a ver com divisão / adição de páginas, mas alguém que conhece melhor o MySQL terá que preencher lá.
Para começar, pode valer a pena tentar adquirir explicitamente um bloqueio de tabela imediatamente para a instrução delete. Consulte TABELAS DE BLOQUEIO e problemas de bloqueio de tabela .
fonte
Você pode tentar que esse
delete
trabalho funcione inserindo primeiro a chave de cada linha a ser excluída em uma tabela temporária como este pseudocódigoQuebrar dessa maneira é menos eficiente, mas evita a necessidade de manter um bloqueio de intervalo de teclas durante o
delete
.Além disso, modifique suas
select
consultas para adicionar umawhere
cláusula excluindo linhas com mais de 900 segundos. Isso evita a dependência do trabalho cron e permite reagendá-lo para execução com menos frequência.Teoria sobre os deadlocks: Eu não tenho muito conhecimento de fundo no MySQL, mas aqui vai ... Ele
delete
manterá um bloqueio de intervalo de chaves por data e hora, para impedir que linhas correspondentes à suawhere
cláusula sejam adicionadas no meio da transação e, ao encontrar linhas para excluir, tentará obter um bloqueio em cada página que está modificando. Eleinsert
adquirirá um bloqueio na página em que está inserindo e, em seguida, tentará adquirir o bloqueio de chave. Normalmenteinsert
, esperará pacientemente a abertura da chave, mas isso trará umdelete
travamento se tentar bloquear a mesma página queinsert
está sendo usada, porque asdelete
necessidades dessa página são bloqueadas e asinsert
que precisam ser bloqueadas. No entanto, isso não parece adequado para as inserções,delete
einsert
estão usando intervalos de data e hora que não se sobrepõem, então talvez algo mais esteja acontecendo.http://dev.mysql.com/doc/refman/5.1/en/innodb-next-key-locking.html
fonte
Caso alguém ainda esteja lutando com esse problema:
Eu enfrentei um problema semelhante, onde 2 pedidos estavam atingindo o servidor ao mesmo tempo. Não houve situação como abaixo:
Então, fiquei perplexo porque o impasse está acontecendo.
Então, descobri que havia relação de pai-filho entre duas tabelas por causa da chave estrangeira. Quando eu estava inserindo um registro na tabela filho, a transação estava adquirindo um bloqueio na linha da tabela pai. Imediatamente depois disso, eu estava tentando atualizar a linha pai, que estava acionando a elevação do bloqueio para uma EXCLUSIVA. Como a segunda transação simultânea já estava mantendo um bloqueio SHARED, estava causando um conflito.
Consulte: https://blog.tekenlight.com/2019/02/21/database-deadlock-mysql.html
fonte
Para programadores Java que usam Spring, evitei esse problema usando um aspecto de AOP que tenta novamente automaticamente transações que são executadas em conflitos transitórios.
Consulte Javadoc @RetryTransaction para obter mais informações.
fonte
Eu tenho um método, cujos internos estão envolvidos em um MySqlTransaction.
O problema do impasse apareceu para mim quando eu executei o mesmo método em paralelo consigo mesmo.
Não houve problema ao executar uma única instância do método.
Quando removi o MySqlTransaction, consegui executar o método paralelamente, sem problemas.
Apenas compartilhando minha experiência, não estou defendendo nada.
fonte
cron
é perigoso. Se uma instância do cron falhar ao terminar antes da próxima, é provável que elas se enfrentem.Seria melhor ter um trabalho em execução contínua que excluísse algumas linhas, dormisse algumas e depois repetisse.
Além disso,
INDEX(datetime)
é muito importante para evitar conflitos.Mas, se o teste de data e hora incluir mais de, digamos, 20% da tabela,
DELETE
ele fará uma varredura na tabela. Pedaços menores excluídos com mais frequência são uma solução alternativa.Outro motivo para usar blocos menores é bloquear menos linhas.
Bottom line:
INDEX(datetime)
Outras técnicas de exclusão: http://mysql.rjweb.org/doc.php/deletebig
fonte