Trabalhando em torno do erro MySQL “Deadlock encontrado ao tentar se trancar; tente reiniciar a transação ”

StackOverflow https://stackoverflow.com/questions/2596005

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:

  1. O que exatamente a minha situação está causando o erro acima?

  2. 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.

Foi útil?

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
    }
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top