MySQL エラー「ロックを取得しようとしたときにデッドロックが見つかりました。トランザクションを再開してみてください」
-
25-09-2019 - |
質問
約 5,000,000 行を含む MySQL テーブルがあり、DBI 経由で接続する並列 Perl プロセスによって小さな方法で常に更新されています。テーブルには約 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 つあります。
私の状況の何が上記のエラーを引き起こしているのでしょうか?
これを回避したり、頻度を減らす簡単な方法はありますか?たとえば、「Db.pm 行 276 でトランザクションを再開する」とは具体的にどうすればよいでしょうか?
前もって感謝します。
解決
あなたは、InnoDBのか、任意の行レベルのトランザクションRDBMSを使用している場合、それが可能であることは、ののすべてののの書き込みトランザクションでも完全に正常な状況では、デッドロックを引き起こす可能性があることを。大きなテーブル、より大きな書き込み、および長いトランザクションブロックがしばしば発生してデッドロックの可能性を高めるだろう。あなたの状況では、それはおそらく、これらの組み合わせです。
本当にデッドロックを処理するための唯一の方法は、それらを期待するコードを記述することです。データベースのコードがよく書かれている場合、これは一般的に非常に難しいことではありません。多くの場合、あなただけのクエリの実行ロジックの周りtry/catch
を入れて、エラーが発生したときにデッドロックを探すことができます。あなたは1をキャッチした場合、行うには、通常のものがちょうど再び失敗したクエリを実行しようとしている。
私は非常にあなたがこのページを読むことをお勧めしますMySQLのマニュアル。これは、デッドロックに対処し、その頻度を減らすためにやるべき事のリストを持っています。
他のヒント
は、デッドロックを処理する方法のただしperlのドキュメントは、ビットまばらで、おそらくPrintError、RAISEERRORとのHandleErrorオプションで混乱します。あなたのコードをラップし、エラーをチェックするために小型:それはそれではなく、のHandleError、[印刷と上げてみてくださいのような使用何かに利用していくと思われます。コードの下デシベルコードはそのエラーが発生したSQL文を再実行します3秒ごとにwhileループの内側にある例を示します。 catchブロックは、特定のERRメッセージである$ _を取得します。私は、ハンドラ関数のコードは、(それによって、ループを壊す)継続すべき場合、エラーを返す1のホストに対してチェック$を_「dbi_err_handler」または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 $を設定する必要があります希望これは誰かに役立ちます -
あなたが挿入する前に一意性チェックを実行するSELECT FOR UPDATE
を使用する場合はinnodb_locks_unsafe_for_binlog
オプションを有効にしない限り、あなたはすべての競合状態のためのデッドロックを取得することに注意してください。一意性をチェックするためにデッドロックフリー方法は盲目的に影響を受けた行数を確認するために、次に、INSERT IGNORE
を使用して一意のインデックスを持つテーブルに行を挿入することである。
タグ
my.cnf
ファイルに行の下に追加
の innodb_locks_unsafe_for_binlog = 1 の
# 1 - ON
0 - OFF
デッドロック例外の場合には、クエリを再試行のアイデアは良いですが、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
}
}