web-dev-qa-db-ja.com

ロックを取得しようとしたときに見つかったmysqlのデッドロックを回避する方法。トランザクションを再起動してください。

オンラインユーザーを記録するinnoDBテーブルがあります。ページが更新されるたびに更新され、どのページにアクセスしているか、およびサイトへの最後のアクセス日が追跡されます。それから古いレコードを削除するために15分ごとに実行されるcronがあります。

ロックしようとしたときにデッドロックが見つかりました。昨夜、トランザクションを5分間再開してみてください。このテーブルにINSERTを実行したときのようです。誰かがこのエラーを回避する方法を提案できますか?

===編集===

実行されているクエリは次のとおりです。

最初のサイトへのアクセス:

INSERT INTO onlineusers SET
ip = 123.456.789.123,
datetime = now(),
userid = 321,
page = '/thispage',
area = 'thisarea',
type = 3

ページを更新するたびに、

UPDATE onlineusers SET
ips = 123.456.789.123,
datetime = now(),
userid = 321,
page = '/thispage',
area = 'thisarea',
type = 3
WHERE id = 888

15分ごとにCron:

DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND

それはそれからいくつかの統計(すなわち、オンラインのメンバー、オンラインの訪問者)を記録するためにいくつかのカウントを行います。

239
David

ほとんどのデッドロックを解決するのに役立つ簡単なトリックの1つは、操作を特定の順序でソートすることです。

2つのトランザクションが2つのロックを逆の順序でロックしようとすると、デッドロックが発生します。

  • 接続1:キー(1)をロックし、キー(2)をロックします。
  • 接続2:キー(2)をロックし、キー(1)をロックします。

両方が同時に実行されると、接続1はキー(1)をロックし、接続2はキー(2)をロックし、各接続は他方がキーを解放するのを待ちます - >デッドロック。

接続が同じ順序でキーをロックするようにクエリを変更したとします。

  • 接続1:キー(1)をロックし、キー(2)をロックします。
  • 接続2:キーをロックする(1)、キーをロックする(2) ;

デッドロックを起こすことは不可能でしょう。

だからこれは私が提案するものです:

  1. Deleteステートメントを除いて、一度に複数のキーへのアクセスをロックする照会が他にないことを確認してください。もしそうなら(そして私はあなたがそう思う)、昇順でそれらのWHEREを(k1、k2、.. kn)に並べる。

  2. 削除ステートメントが昇順で動作するように修正します。

変化する

DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND

DELETE FROM onlineusers WHERE id IN (SELECT id FROM onlineusers
    WHERE datetime <= now() - INTERVAL 900 SECOND order by id) u;

留意すべきもう1つのことは、mysqlのドキュメントでは、デッドロックが発生した場合にクライアントが自動的に再試行するように推奨されていることです。このロジックをクライアントコードに追加することができます。 (言う、あきらめる前に、この特定のエラーについて3回再試行します)。

252
Omry Yadan

デッドロックは、2つのトランザクションが互いにロックを獲得するのを待つときに発生します。例:

  • Tx 1:AをロックしてからB
  • Tx 2:BをロックしてからA

デッドロックについては数多くの質問と回答があります。行を挿入/更新/削除するたびに、ロックが取得されます。デッドロックを回避するには、同時トランザクションがデッドロックを引き起こす可能性がある順序で行を更新しないようにする必要があります。一般的に言って、は異なるトランザクションでさえも常に同じ順序でロックを取得しようとします(例:常にテーブルAが最初、次にテーブルB)。

データベースのデッドロックのもう1つの理由は、インデックスの欠落です。行が挿入/更新/削除されると、データベースはリレーショナル制約をチェックする必要があります。つまり、関係が一貫していることを確認します。そのためには、データベースは関連テーブルの外部キーをチェックする必要があります。 すると、変更された行以外の他のロックが取得される可能性があります。そうするとrow lockではなくtable lockという結果になる可能性があります。 。テーブルロックが発生した場合、ロックの競合が大きくなり、デッドロックの可能性が高まります。

64
ewernli

Deleteステートメントは、テーブル内の合計行の大部分に影響を与える可能性があります。最終的には、削除時にテーブルロックが取得される可能性があります。ロック(この場合は行ロックまたはページロック)を保持してさらにロックを取得することは、常にデッドロックのリスクです。しかし、insert文がロックのエスカレーションを引き起こす理由を説明することはできません - それはページの分割/追加に関係しているかもしれませんが、MySQLをもっとよく知っている人はそこに記入しなければならないでしょう。

最初に、deleteステートメントのためにテーブルロックをすぐに明示的に取得しようとすることは価値があります。 ロックテーブル および テーブルロックの問題 を参照してください。

9
Anders Abel

Springを使用しているJavaプログラマーにとって、一時的なデッドロックに陥るトランザクションを自動的に再試行するAOPアスペクトを使用して、この問題を回避しました。

詳細については @ RetryTransaction Javadocを参照してください。

3
Archie

誰かがまだこの問題に苦しんでいるならば:

私は2つの要求が同時にサーバーに当たっているという同様の問題に直面しました。以下のような状況はありませんでした。

T1:
    BEGIN TRANSACTION
    INSERT TABLE A
    INSERT TABLE B
    END TRANSACTION

T2:
    BEGIN TRANSACTION
    INSERT TABLE B
    INSERT TABLE A
    END TRANSACTION

だから、なぜデッドロックが起こっているのか困惑しました。

それから、外部キーのため、2つのテーブルの間に親子関係シップがあることがわかりました。子テーブルにレコードを挿入していたときに、トランザクションが親テーブルの行のロックを取得していました。その直後、ロックの昇格をトリガーしていた親行をEXCLUSIVEに更新しようとしました。 2番目の同時トランザクションが既にSHAREDロックを保持していたため、デッドロックが発生していました。

参照してください: https://blog.tekenlight.com/2019/02/21/database-deadlock-mysql.html

1
chatsap

私はメソッドを持っています。その内部はMySqlTransactionにラップされています。

同じ方法をそれ自体と並行して実行したときに、デッドロックの問題が発生しました。

メソッドの単一インスタンスの実行に問題はありませんでした。

MySqlTransactionを削除したとき、私は問題なくそれ自体と並行してメソッドを実行することができました。

私の経験を共有するだけで、私は何も主張していません。

0
CINCHAPPS