2

Mysql 5.1 データベースに約 2,700 万行の InnoDB テーブルがあります。このテーブルにはmediumint unsigned、定期的にグローバルに「0」にリセットできるようにしたいインデックスのない列が 3 つあります。例えば:

update myTable set countA = 0;

この非常に単純な更新クエリは、InnoDB の行レベル ロックの問題に直面しています。あまりにも多くの行をロックした後、更新クエリは十分に文書化されたエラーで失敗します:

ERROR 1206 (HY000): The total number of locks exceeds the lock table size

問題は、このような大規模なテーブルでは、個々の行ロックの数が、ロックを格納するために割り当てられたスペースを超えていることです。

この問題に対処する方法について、いくつかの提案を見つけました。

テーブル全体をロックして行ロックをオフにする
これは最善かつ最もクリーンな解決策のように思えましたが、これらのまれな操作中にこの特定のテーブルが数分間ロックされても問題はありません。問題は、与えられた解決策が実際にはうまくいかなかったことです。多分それはMysqlの古いバージョンで動作するために使用されるものですか?

ロック バッファのサイズを
大きくする Mysql 変数の値を大きくすることで、innodb_buffer_pool_size行ロックのためのスペースを増やすことができます。十分なスペースを割り当てることができたとしても、テーブルが大きくなると失敗する可能性があるため、このソリューションには非常に不快です。また、間違いなく不必要な数ギガバイトのロックを作成する必要がある、貧弱なセットアップのようです。

影響を受ける列にインデックスを付けます(コメントを参照)
適切なインデックスでサポートされている単一の列に対して一括更新を行う場合、InnoDB はすべての行のロックを回避できます。インデックスを使用することで、影響を受ける行のみをロックできます。実際にこれを試してみましたが、これら 3 つのインデックスを管理すると、増分更新が大幅に遅くなることがわかりました。カウントをリセットする必要があるすべてのインスタンスに対してこれら 3 つのカウントを調整する数千万の更新クエリがあるため、インクリメンタル更新の効率を犠牲にしたくありません。

バッチで列を更新する
ソース ドキュメントではこれを回避策として説明していますが、ある時点までは非常に効果的であることがわかりました。

update myTable set countA = 0 where countA != 0 limit 500000;

影響を受ける行の数が指定された数より少なくなるまでこれを繰り返すことでlimit、すべての行が更新されます。Mysqlが一致する行をさらに探す必要があるため、1回の反復で更新できる行の数が急激に減少するため、このソリューションは特に大きなテーブルではうまくいきませんでした。更新される 1,000 行が 1 回の実行には多すぎるまでに、何百万ものゼロ以外の値を更新する必要がありました。

では、私にはどのような可能性が残されているのでしょうか?

  1. InnoDB の使用をやめる: これには、現在のプロセスを再編成する必要がありますが、検討したいと思います。
  2. カウント列をメイン テーブルから移動する: CountA テーブルがある場合は、を使用してカウントをリセットdelete from CountAし、メイン テーブルに対する内部結合を使用してカウントを取得できます。これにより、CountA テーブルの行を条件付きで更新または挿入する前に、メイン テーブルから ID を取得する必要があるため、個々のカウントの更新が遅くなります。素晴らしいことではありませんが、私が考慮したいことがあります。
  3. クリーンなソリューションであり、テーブルで適度に成長することが期待できる他のものはありますか?

更新: 受け入れられた応答の助けを借りて、約 5 分で作業を完了するバッチ処理の実装ができました。バッチ処理は必要ない方がいいと思いますが、より直接的な解決策が見つかるまでは必要なようです。次の人がこの質問につまずくのに役立つ場合に備えて、関連する Java JDBC コードを次に示します。(受け入れられた回答からリンクされたブログ投稿も読むことをお勧めします。)

    int batchsize = 10_000;
    PreparedStatement pstmt = connection.prepareStatement
            ("UPDATE tableName SET countA = 0, countB = 0, countC = 0 "
                       + "WHERE id BETWEEN ? AND ?");
    for (int left = 0; left < maxId; left += batchsize) {
        pstmt.setInt(1, left + 1);
        pstmt.setInt(2, left + batchsize);
        pstmt.executeUpdate();
    }
    pstmt.close();
4

1 に答える 1

2

プランA

チャンク(バッチ処理)が好きです。ただし、コードのスケッチはあまり効率的ではありません。追加OFFSETしても役に立ちません。代わりに、テーブルを慎重に歩くことについての私のブログを参照してください。つまり、「次の」100〜1000行を検索します。を実行しUPDATEます。ループ。(注: 各チャンクは独自のトランザクションである必要があります。)

「次の N 行を見つけて、中断した場所を思い出す」ための手法は、PRIMARY KEY. 私のブログでは、ほとんどのシナリオ (数値、文字列、スパースなど) をカバーしています。(このブログでは についてDELETE説明していますが、簡単に に適応できるはずUPDATEです。)

PRIMARY KEYInnoDB はクラスター化されているため、チャンク化に役立ちます。したがって、各チャンクは最小数のブロックを読み取る必要があります。

次の手段

並列テーブルを使用する (「メイン テーブルから count 列を移動する」) ことはおそらく良い考えです。これは、触れるディスク ブロックの数が少なくなるためです。同じPRIMARY KEY(sans AUTO_INCREMENT) を使用します。

プランC

(1) 並列テーブル (プラン B など)、および (2) 欠落している行は値 = 0 を意味します。TRUNCATE TABLE次に、 (プランAとは異なり)クリアリングが達成されます。クリアする列が 3 つあるため、ルールは次のようになります。

  • 値がゼロ以外に変更された場合は、その行がパラレル テーブルに存在することを確認し、必要に応じて値を設定します (その他の値にはゼロを追加します)。おそらくINSERT ... ON DUPLICATE KEY UPDATE...最適です。
  • SELECT値 ( )を探すときは、LEFT JOINおよびを実行IFNULL(col, 0)して値または 0 を取得します。

プラン X (非スターター)

列にインデックスを付けると問題が発生します -- インデックス付きの列を更新する場合、データとインデックスの両方を変更する必要があります。

于 2015-04-21T00:51:31.203 に答える