ロックを取得しようとしたときにmysql 'Deadlockが検出されないようにする方法。トランザクションを再開してください」


286

オンラインユーザーを記録する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

次に、いくつかの統計を記録するためにいくつかのカウントを実行します(つまり、オンラインのメンバー、オンラインの訪問者)。


テーブル構造についてもう少し詳しく教えてもらえますか?クラスター化インデックスまたは非クラスター化インデックスはありますか?
Anders Abel

13
dev.mysql.com/doc/refman/5.1/en/innodb-deadlocks.html-「show engine innodb status」を実行すると、有用な診断が得られます。
マーティン

ユーザーがページを踏んだときに同期データベース書き込みを行うことはお勧めしません。それを行う正しい方法は、memcacheや高速キューなどのメモリに保存し、cronを使用してdbに書き込むことです。
ニール

回答:


292

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

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

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

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

ここで、接続が同じ順序でキーをロックするようにクエリを変更した場合、つまり:

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

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

これが私が提案するものです:

  1. 削除ステートメントを除いて、一度に複数のキーにアクセスをロックするクエリが他にないことを確認してください。行う場合(私はそうしていると思います)、それらの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回再試行します)。


2
Transaction(autocommit = false)があると、デッドロック例外がスローされます。同じstatement.executeUpdate()を再試行するだけで十分ですか、それともトランザクション全体がgimpされており、その中で実行されていたすべてをロールバック+再実行する必要がありますか?
Whome

5
トランザクションを有効にしている場合、それはすべてかゼロかです。何らかの例外があった場合、トランザクション全体が影響を及ぼさないことが保証されます。その場合は、すべてを再起動する必要があります。
Omry Yadan 2014

4
巨大なテーブルの選択に基づく削除は、単純な削除よりも非常に遅くなります
Thermech

3
どうもありがとうございました。'sortステートメント'のヒントにより、デッドロックの問題が修正されました。
Miere、2015年

4
@OmryYadan私が知っていることから、MySQLでは、UPDATEを実行しているのと同じテーブルからサブクエリで選択することはできません。dev.mysql.com/doc/refman/5.7/en/update.html
artaxerxe

72

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

  • Tx 1:AをロックしてからBをロックする
  • Tx 2:ロックB、次にA

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

データベースのデッドロックのもう1つの理由は、インデックス欠落していることです。行が挿入/更新/削除されると、データベースは関係制約をチェックする必要があります。つまり、関係が一貫していることを確認します。これを行うには、データベースで関連テーブルの外部キーをチェックする必要があります。これは、可能性が変更された行より取得された他のロックをもたらします。その場合、常に外部キー(およびもちろん主キー)にインデックスがあることを確認してください。そうしないと、行ロックではなくテーブルロックが発生する可能性があります。テーブルロックが発生すると、ロックの競合が高くなり、デッドロックの可能性が高くなります。


3
おそらく私の問題は、ユーザーがページを更新し、cronがレコードに対してDELETEを実行しようとすると同時にレコードのUPDATEをトリガーしたことです。ただし、INSERTSでエラーが発生するので、cronは作成されたばかりのレコードを削除しません。では、まだ挿入されていないレコードでデッドロックが発生するのはなぜですか。
デビッド

テーブルとトランザクションが正確に行うことについて、もう少し情報を提供できますか?
ewernli 2010

トランザクションごとに1つのステートメントしかない場合、デッドロックがどのように発生するかわかりません。他のテーブルで他の操作はありませんか?特別な外部キーや一意の制約はありませんか?カスケード削除制約はありませんか?
ewernli 2010

いいえ、他に特別なものはありません...テーブルの使用法の性質に起因すると思います。訪問者がページを更新するたびに行が挿入または更新されます。一度に約1000人以上の訪問者が訪れます。
David

12

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

まず、削除ステートメントのテーブルロックをすぐに明示的に取得することを試みる価値があります。LOCK TABLESおよびテーブルのロックの問題を参照してください。


6

deleteこの疑似コードのように、削除する各行のキーを一時テーブルに最初に挿入して、そのジョブを操作してみることができます

create temporary table deletetemp (userid int);

insert into deletetemp (userid)
  select userid from onlineusers where datetime <= now - interval 900 second;

delete from onlineusers where userid in (select userid from deletetemp);

このように分割することは効率が悪くなりますが、キーレンジロックを保持する必要がなくなります。 deleteます。

また、selectクエリを変更して、where、900秒より古い行を除外する句を。これにより、cronジョブへの依存が回避され、実行頻度を下げるように再スケジュールできます。

デッドロックに関する理論:MySQLには多くの背景がありませんが、次のようになります... トランザクションの途中で、deleteそのwhere句に一致する行が追加されないように、日時のキー範囲ロックを保持します、削除する行を見つけると、変更中の各ページでロックを取得しようとします。insertそれはに挿入されているページのロックを取得しようとされた後、キーロックを取得しようとします。通常、insertはそのキーロックが開くまで辛抱強く待機しますが、がそのページロックとそのキーロックを必要とするため、が使用してdeleteいる同じページをロックしようとすると、デッドロックが発生します。これは、かかわらず、挿入のために右のようではありませんし、insertdeleteinsertdeleteinsert 重複しない日時範囲を使用しているため、別の問題が発生している可能性があります。

http://dev.mysql.com/doc/refman/5.1/en/innodb-next-key-locking.html


4

誰かがまだこの問題で苦労している場合:

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番目の同時トランザクションがすでに共有ロックを保持していたため、デッドロックが発生していました。

参照:https : //blog.tekenlight.com/2019/02/21/database-deadlock-mysql.html


私の場合も、問題は外部キー関係だったようです。ありがとう1
クリス・プリンス

3

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

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


0

私にはメソッドがあり、その内部はMySqlTransactionにラップされています。

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

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

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

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


0

cron危険です。cronの1つのインスタンスが次のインスタンスの期限が切れる前に終了しない場合、それらは互いに戦う可能性があります。

一部の行を削除し、一部をスリープしてから繰り返すジョブを継続的に実行することをお勧めします。

また、INDEX(datetime)デッドロックを回避するためにも非常に重要です。

ただし、日時テストにテーブルの20%以上が含まれている場合、はDELETEテーブルスキャンを実行します。小さなチャンクが頻繁に削除されるのが回避策です。

チャンクを小さくするもう1つの理由は、ロックする行を少なくすることです。

結論:

  • INDEX(datetime)
  • 継続的に実行中のタスク-削除、1分間スリープ、繰り返し。
  • 上記のタスクが停止していないことを確認するには、失敗時に再起動することを唯一の目的とするcronジョブを用意します。

その他の削除手法:http : //mysql.rjweb.org/doc.php/deletebig

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.