解决MySQL错误"尝试获取锁定时发现死锁;尝试重新启动事务"

Ano*_*don 29 mysql deadlock database-deadlocks

我有一个大约有5,000,000行的MySQL表,通过DBI连接的并行Perl进程以小的方式不断更新.该表有大约10列和几个索引.

一个相当常见的操作有时会产生以下错误:

DBD::mysql::st execute failed: Deadlock found when trying to get lock; try restarting transaction at Db.pm line 276.
Run Code Online (Sandbox Code Playgroud)

触发错误的SQL语句是这样的:

UPDATE file_table SET a_lock = 'process-1234' WHERE param1 = 'X' AND param2 = 'Y' AND param3 = 'Z' LIMIT 47
Run Code Online (Sandbox Code Playgroud)

该错误仅在有时触发.我估计只有1%或更少的电话.然而,它从未发生在一个小桌子上,随着数据库的增长而变得越来越普遍.

请注意,我正在使用file_table中的a_lock字段来确保我运行的四个几乎相同的进程不会尝试在同一行上工作.该限制旨在将他们的工作分解成小块.

我没有在MySQL或DBD :: mysql上做太多调整.MySQL是标准的Solaris部署,数据库连接设置如下:

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;
Run Code Online (Sandbox Code Playgroud)

我在网上看到其他几个人报告了类似的错误,这可能是一个真正的僵局.

我有两个问题:

  1. 究竟是什么情况导致上述错误?

  2. 有一种简单的方法来解决它或减少它的频率?例如,我究竟如何"在Db.pm第276行重新启动交易"?

提前致谢.

zom*_*bat 72

如果您正在使用InnoDB或任何行级事务RDBMS,则任何写入事务都可能导致死锁,即使在完全正常的情况下也是如此.较大的表,较大的写入和较长的事务块通常会增加发生死锁的可能性.在你的情况下,它可能是这些的组合.

真正处理死锁的唯一方法是编写代码以期望它们.如果您的数据库代码写得很好,这通常不是很困难.通常,您可以放置try/catch查询执行逻辑,并在发生错误时查找死锁.如果你抓到一个,通常要做的就是尝试再次执行失败的查询.

我强烈建议您在MySQL手册中阅读本页.它有一系列的事情要做,以帮助应对死锁并降低其频率.

  • 那么我们需要捕获哪些错误代码?单独捕获1205足够吗?http://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html中有900多个错误代码.您如何知道我们需要捕获的所有代码,以便为您的try/catch建议实施正确的解决方案? (4认同)
  • @Pacerier,我相信 1213 也是必须的 - 它比 1205 更频繁地发生在我身上。请参阅 https://mariadb.com/kb/en/mariadb-error-codes/ 。也许 3058 也应该重试。 (2认同)

Ros*_*oss 10

答案是正确的,但是关于如何处理死锁的perl文档有点稀疏,可能会与PrintError,RaiseError和HandleError选项混淆.似乎不是使用HandleError,而是使用Print和Raise,然后使用Try:Tiny之类的东西来包装代码并检查错误.下面的代码给出了一个示例,其中db代码位于while循环中,该循环将每3秒重新执行一次错误的sql语句.catch块获取$ _,这是特定的错误消息.我把它传递给一个处理函数"dbi_err_handler",它检查$ _对多个错误,如果代码应该继续(从而打破循环)则返回1,如果它是死锁则返回0并且应该重试...

$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;}
   }
}
Run Code Online (Sandbox Code Playgroud)

dbi_err_handler应至少具有以下内容:

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;
}
Run Code Online (Sandbox Code Playgroud)

您应该包含您希望处理的其他错误,并根据您是要重新执行还是继续来设置$ retval.

希望这有助于某人 -


小智 8

请注意,如果您SELECT FOR UPDATE在插入之前使用执行唯一性检查,则除非启用该innodb_locks_unsafe_for_binlog选项,否则每个竞争条件都会出现死锁.检查唯一性的无死锁方法是盲目地将行插入具有唯一索引的表中INSERT IGNORE,然后检查受影响的行计数.

将以下行添加到my.cnf文件中

innodb_locks_unsafe_for_binlog = 1

#

1 - 开
0 - 关

#

  • 启用`innodb_locks_unsafe_for_binlog`可能会导致幻像问题,因为其他会话可以在禁用间隙锁定时将新行插入间隙. (2认同)