35

DBIを介して接続する並列Perlプロセスによって小さな方法で絶えず更新されている約5,000,000行のMySQLテーブルがあります。テーブルには、約 10 の列といくつかのインデックスがあります。

1 つのかなり一般的な操作で、次のエラーが発生することがあります。

DBD::mysql::st execute failed: Deadlock found when trying to get lock; try restarting transaction at Db.pm line 276.

エラーをトリガーする SQL ステートメントは次のようなものです。

UPDATE file_table SET a_lock = 'process-1234' WHERE param1 = 'X' AND param2 = 'Y' AND param3 = 'Z' LIMIT 47

エラーはたまにしか発生しません。コールの 1% 以下で見積もっています。ただし、小さなテーブルでは発生せず、データベースが大きくなるにつれてより一般的になりました.

file_table の a_lock フィールドを使用して、実行中の 4 つのほぼ同一のプロセスが同じ行で試行および動作しないようにしていることに注意してください。制限は、作業を小さなチャンクに分割するように設計されています。

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;

他の何人かが同様のエラーを報告しており、これが本当のデッドロック状況である可能性があることをオンラインで見ました。

2 つの質問があります。

  1. 上記のエラーの原因は、私の状況について正確には何ですか?

  2. それを回避したり、頻度を減らす簡単な方法はありますか? たとえば、「Db.pm 行 276 でトランザクションを再開する」にはどうすればよいですか?

前もって感謝します。

4

4 に答える 4

78

InnoDB または行レベルのトランザクション RDBMS を使用している場合、完全に正常な状況であっても、書き込みトランザクションによってデッドロックが発生する可能性がありますテーブルが大きく、書き込みが大きく、トランザクション ブロックが長いと、多くの場合、デッドロックが発生する可能性が高くなります。あなたの状況では、おそらくこれらの組み合わせです。

デッドロックを真に処理する唯一の方法は、デッドロックを予期するようにコードを記述することです。データベース コードが適切に記述されていれば、これは通常、それほど難しくありません。多くの場合、クエリ実行ロジックを単に配置しtry/catchて、エラーが発生したときにデッドロックを探すことができます。1 つをキャッチした場合、通常行うべきことは、失敗したクエリを再度実行することです。

MySQL マニュアルのこのページを読むことを強くお勧めします。デッドロックに対処し、その頻度を減らすために行うべきことのリストがあります。

于 2010-04-07T21:33:26.153 に答える
13

答えは正しいですが、デッドロックの処理方法に関する perl ドキュメントは少しまばらで、おそらく PrintError、RaiseError、および HandleError オプションと混乱します。HandleError を使用するのではなく、Print と Raise を使用してから、Try:Tiny などを使用してコードをラップし、エラーをチェックしているようです。以下のコードは、エラーが発生した sql ステートメントを 3 秒ごとに再実行する while ループ内に db コードがある例を示しています。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;}
   }
}

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;
}

処理したい他のエラーを含め、再実行するか続行するかに応じて $retval を設定する必要があります。

これが誰かに役立つことを願っています-

于 2012-02-16T17:01:48.060 に答える
8

SELECT FOR UPDATE挿入の前に一意性チェックを実行するために使用する場合、innodb_locks_unsafe_for_binlogオプションを有効にしない限り、すべての競合状態でデッドロックが発生することに注意してください。一意性を確認するためのデッドロックのない方法は、 を使用して一意のインデックスを持つテーブルに行をやみくもに挿入しINSERT IGNORE、影響を受ける行数を確認することです。

my.cnf以下の行をファイルに追加

innodb_locks_unsafe_for_binlog = 1

#

1 - オン
0 - オフ

#
于 2012-02-16T14:50:11.787 に答える
1

デッドロック例外の場合にクエリを再試行するという考えは良いですが、mysql クエリはロックが解放されるのを待ち続けるため、非常に遅くなる可能性があります。また、デッドロックが発生した場合、mysql はデッドロックがあるかどうかを検出しようとします。デッドロックがあることを検出した後でも、デッドロック状態から抜け出すためにスレッドを追い出す前にしばらく待機します。

この状況に直面したときに私がしたことは、独自のコードにロックを実装することでした。これは、バグが原因で mysql のロック メカニズムが失敗しているためです。そこで、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
    }
}
于 2013-03-30T07:34:38.530 に答える