Trabalhando em torno do erro MySQL “Deadlock encontrado ao tentar se trancar; tente reiniciar a transação ”
-
25-09-2019 - |
Pergunta
Eu tenho uma tabela MySQL com cerca de 5.000.000 de linhas que estão sendo atualizadas constantemente de pequenas maneiras por processos Perl paralelos que se conectam via DBI. A tabela possui cerca de 10 colunas e vários índices.
Uma operação bastante comum dá origem ao seguinte erro às vezes:
DBD::mysql::st execute failed: Deadlock found when trying to get lock; try restarting transaction at Db.pm line 276.
A afirmação SQL que desencadeia o erro é algo assim:
UPDATE file_table SET a_lock = 'process-1234' WHERE param1 = 'X' AND param2 = 'Y' AND param3 = 'Z' LIMIT 47
O erro é acionado apenas às vezes. Eu estimaria em 1% das chamadas ou menos. No entanto, isso nunca aconteceu com uma pequena mesa e se tornou mais comum à medida que o banco de dados cresceu.
Observe que estou usando o campo A_LOCK em FILE_TABLE para garantir que os quatro processos quase idênticos que estou executando não tente e trabalhe na mesma linha. O limite foi projetado para dividir seu trabalho em pequenos pedaços.
Não fiz muito ajuste no MySQL ou DBD :: MySQL. O MySQL é uma implantação padrão da Solaris, e a conexão do banco de dados é configurada da seguinte forma:
my $dsn = "DBI:mysql:database=" . $DbConfig::database . ";host=${DbConfig::hostname};port=${DbConfig::port}";
my $dbh = DBI->connect($dsn, $DbConfig::username, $DbConfig::password, { RaiseError => 1, AutoCommit => 1 }) or die $DBI::errstr;
Vi on -line que várias outras pessoas relataram erros semelhantes e que isso pode ser uma situação genuína de impasse.
Eu tenho duas perguntas:
O que exatamente a minha situação está causando o erro acima?
Existe uma maneira simples de contorná -lo ou diminuir sua frequência? Por exemplo, como exatamente eu faço "reiniciar a transação na linha 276 do db.pm"?
Desde já, obrigado.
Solução
Se você estiver usando o Innodb ou qualquer RDBMS transacional em nível de linha, é possível que algum A transação de gravação pode causar um impasse, mesmo em situações perfeitamente normais. Tabelas maiores, gravações maiores e longos blocos de transação geralmente aumentam a probabilidade de impulsionar os deadlocks. Na sua situação, provavelmente é uma combinação dessas.
A única maneira de lidar verdadeiramente a deadlocks é escrever seu código para esperá -los. Isso geralmente não é muito difícil se o código do seu banco de dados estiver bem escrito. Muitas vezes você pode simplesmente colocar um try/catch
Em torno da lógica de execução da consulta e procure um impasse quando ocorrer erros. Se você pegar um, a coisa normal a fazer é apenas tentar executar a consulta fracassada novamente.
Eu recomendo que você leia esta página no manual do MySQL. Ele tem uma lista de coisas a fazer para ajudar a lidar com os impasse e reduzir sua frequência.
Outras dicas
A resposta está correta, no entanto, a documentação do Perl sobre como lidar com impasse é um pouco escassa e talvez confusa com as opções Printerror, RaiseError e HandleError. Parece que, em vez de ir com o HandleRor, usar na impressão e levantar e depois usar algo como tente: minúsculo para embrulhar seu código e verificar se há erros. O código abaixo dá um exemplo em que o código DB está dentro de um loop de um tempo que reexecionará uma instrução SQL errada a cada 3 segundos. O bloco de captura recebe $ _, que é a mensagem de erro específica. Passo isso para uma função de manipulador "dbi_err_handler", que verifica $ _ contra uma série de erros e retorna 1 se o código continuar (quebrando assim o loop) ou 0 se for um impasse e deve ser julgado ...
$sth = $dbh->prepare($strsql);
my $db_res=0;
while($db_res==0)
{
$db_res=1;
try{$sth->execute($param1,$param2);}
catch
{
print "caught $_ in insertion to hd_item_upc for upc $upc\n";
$db_res=dbi_err_handler($_);
if($db_res==0){sleep 3;}
}
}
dbi_err_handler deve ter pelo menos o seguinte:
sub dbi_err_handler
{
my($message) = @_;
if($message=~ m/DBD::mysql::st execute failed: Deadlock found when trying to get lock; try restarting transaction/)
{
$caught=1;
$retval=0; # we'll check this value and sleep/re-execute if necessary
}
return $retval;
}
Você deve incluir outros erros que deseja manusear e definir $ retval, dependendo se você deseja reexecionar ou continuar ..
Espero que isso ajude alguém -
Observe que se você usar SELECT FOR UPDATE
Para realizar uma verificação de singularidade antes de uma inserção, você receberá um impasse para cada condição de corrida, a menos que possibilite o innodb_locks_unsafe_for_binlog
opção. Um método sem deadlock para verificar a singularidade é inserir cegamente uma linha em uma mesa com um índice exclusivo usando INSERT IGNORE
, depois verificar a contagem de linhas afetadas.
Adicione a linha abaixo my.cnf
Arquivo
innodb_locks_unsafe_for_binlog = 1
#1 - on
0 - OFF
A idéia de tentar novamente a consulta em caso de exceção de impasse é boa, mas pode ser terrivelmente lenta, pois o MySQL Query continuará esperando que as fechaduras sejam lançadas. E no caso de Deadlock Mysql está tentando descobrir se há algum impasse, e mesmo depois de descobrir que há um impasse, ele espera um tempo antes de expulsar um fio para sair da situação de impasse.
O que fiz quando enfrentei essa situação é implementar o bloqueio em seu próprio código, pois é o mecanismo de bloqueio do MySQL está falhando devido a um bug. Então, implementei meu próprio bloqueio de nível de linha no meu código Java:
private HashMap<String, Object> rowIdToRowLockMap = new HashMap<String, Object>();
private final Object hashmapLock = new Object();
public void handleShortCode(Integer rowId)
{
Object lock = null;
synchronized(hashmapLock)
{
lock = rowIdToRowLockMap.get(rowId);
if (lock == null)
{
rowIdToRowLockMap.put(rowId, lock = new Object());
}
}
synchronized (lock)
{
// Execute your queries on row by row id
}
}