同時に実行されている複数のMySQL更新がロックされ、完了するまでに数分かかるという問題が発生しています。私はInnoDBを使用しているため、更新ごとに1行しか更新されないため、なぜこれが発生するのかについて混乱しています。私はm2.4xlarge RDSインスタンスも使用しています(最大のRDSインスタンスです)。
ここに私がやっていることがあります:約1億行のテーブルがあり、「ビュー」が列(インデックス付き)であり、約100万行のビューを更新します。いくつかの異なるサーバーで、次のようなループがあり、各サーバーには更新する独自の行のセットがあります(疑似コード)。
mysql("set autocommit=0");
mysql("start transaction");
foreach($rows as $row) {
mysql("update table set views=views+1 where id=$row[id]");
}
mysql("commit");
これは、更新する必要があるすべての行をループします。サーバー数が4程度のように少ない場合は完全に機能しますが、サーバー数が10以上に増えると、更新は一度に「更新中」の状態でハングし始めます。ロックを待機しているということは何もありません。単に「更新中」です。これは約5分間発生し、最終的に更新を行ってループを継続し、最終的には再び発生します。
更新を行う別の方法を探しているのではありません。一時テーブルのようなものを持っていると
update table,tmp_table set table.views = table.views+tmp_table.views where
table.id = tmp_table.id
更新されるすべての行を、すべてが完了するまでロックします(これは数時間になる可能性があります)。これは私にとっては機能しません。それらはこれらのひどいループになければなりません。
彼らが「更新中」の状態で動かなくなる理由と、それを防ぐために何ができるのかと思っています。
tldr; 10以上の「更新」ループがあると、最終的にはすべての更新が同時にロックされ、最終的に更新を行ってループを続行することを決定するまで、未知の理由により、数秒後に再度発生します
変数を表示: http://Pastebin.com/NdmAeJrz
エンジンのINNODBステータスを表示: http://Pastebin.com/Ubwu4F1h
更新を行う別の方法を探しているのではありません。 tmpテーブルのようなものがあると、更新されるすべての行がすべて完了するまで(数時間になる可能性があります)ロックされますが、これは私にとっては機能しません。それらはこれらのひどいループになければなりません。
同意しません。
RDBMSの長所は、「これらすべての行plzを更新する」などのセット操作を実行することです。このことを考えると、ごくまれな状況を除いて、これらの「ひどいループ」が最善の方法ではないことが直感でわかるはずです。
現在の更新ロジックを見て、それが何をしているかを理解しましょう。
まず、スクリプトのset autocommit=0
行は不要です。その直後にstart transaction
で明示的にトランザクションを開くため、autocommit
またはCOMMIT
でトランザクションを終了するまで、ROLLBACK
自動的に無効になります 。
ロジックの要点です。これらの個々の更新をすべて1つの大きなトランザクションでループ内にラップしました。反復的な更新の背後にある意図がロックの削減と同時実行性の向上であった場合、ラップされたトランザクションはその意図を無効にします。 MySQLは、トランザクションがコミットするまで更新するすべての行のロックを維持する必要があるため、トランザクションが失敗した場合やキャンセルされた場合に、一度にすべてをロールバックできます。さらに、この行の範囲をロックしようとしていることを事前に知る代わりに(これにより、MySQLが適切な粒度でロックを発行できるようになります)エンジンがラピッドファイアで多数の行レベルのロックを発行することを余儀なくされています。100万行を更新している場合、これは大きな負荷になります。そのエンジン。
私は2つの解決策を提案します。
autocommit
をオンにして、トランザクションラッパーを削除します。MySQLは、行の更新が完了した直後にすべての行ロックを解放できます。それでも、短期間で大量のロックを発行および解放する必要があるため、これが適切な修正になるとは思えません。さらに、ループの途中でエラーが発生した場合、その作業はトランザクションにバインドされていないため、何もロールバックされません。
一時テーブルで更新をバッチ処理します。このソリューションについて言及してから却下しましたが、パフォーマンスが最も良いと思います。もう試しましたか?最初に100万行の更新全体をテストします。それが長すぎる場合は、スイートスポットが見つかるまで、作業を次第に小さなチャンクにバッチ処理します。バッチは、全体の作業をすばやく完了するのに十分な大きさですが、個々のバッチが他のプロセスを長時間ブロックすることはありません。これは 一般的な手法 DBAがライブ操作中に多数の行を変更する必要がある場合に使用します。目標は同時実行性を最大化することなので、autocommit
をオンに保ち、この作業を大規模なトランザクションにラップしないでください。これにより、MySQLがロックを解放します。できるだけ速やかに。
バッチが徐々に小さくなるにつれて、このソリューションは最終的に最初のものに近づくことに注意してください。このため、このソリューションのパフォーマンスが向上すると私は確信しています。データベースエンジンが作業をチャンクにグループ化できると、飛んでいきます。
InnoDBを使用する場合でも、デッドロックの差し迫った脅威が常にあります。この特定のケースでは、ビューテーブルのPRIMARY KEYを介してデータを更新しているため、InnoDBがヘッドロック状態でデッドロック状態に陥っていても、行を見ることができます。これにより、クラスター化インデックス内の積極的なロックが開始されます。
これはSHOW ENGINE INNODB STATUS\G
を使用してロックされていることがわかります
私は同様の問題を扱う非常に難しい3つの質問に答えました。
SELECT/UPDATEクエリは、PRIMARY KEYを介して更新するときに、クラスタードインデックスとも呼ばれる gen_clust_index に対してロックを実行できます。
以下は、これらの質問をした人である @ RedBlueThing で積極的に調べた3つのDBA Stack Exchanges質問です。 @RedBlueThingは彼の質問に対して回避策を見つけました。
これら3つの質問すべてで、行ロックには、同じテーブルのクラスター化インデックス内の対応するロックが含まれていました。ロックされた行の隣接するキーが関係していたため、問題の原因となりました。
MORAL OF THE STORY:InnoDBによるデッドロックは依然として可能です。個々の行レベルのロックと問題の行を個別に更新するための適切なアルゴリズムを設定すると、毎日複数の行レベルのロックを介して一括更新するよりもずっと安全です。
この方法でテーブルを頻繁に更新する場合は、必ずautocommit=1
を使用してください。それでも、InnoDBで行を更新すると、あらゆる種類の [〜#〜] mvcc [〜#〜] データが行の以前の内容の周囲に配置され、同時トランザクションが可能になります。 UPDATEの性質上、多くのMVCCデータが生成されます。
あなたのinnodbステータスを見ると、viewsテーブルを含む最新のデッドロックがこのクエリによるものであることがわかります。
update low_priority reddit_new
join images_new on images_new.hash = reddit_new.hash
set reddit_new.score = images_new.views
where date > date(now() - interval 1 day)
reddit_new.date
インデックス付き?両方のテーブルのハッシュ列にインデックスが付けられていますか?