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 回の実行には多すぎるまでに、何百万ものゼロ以外の値を更新する必要がありました。
では、私にはどのような可能性が残されているのでしょうか?
- InnoDB の使用をやめる: これには、現在のプロセスを再編成する必要がありますが、検討したいと思います。
- カウント列をメイン テーブルから移動する: CountA テーブルがある場合は、を使用してカウントをリセット
delete from CountA
し、メイン テーブルに対する内部結合を使用してカウントを取得できます。これにより、CountA テーブルの行を条件付きで更新または挿入する前に、メイン テーブルから ID を取得する必要があるため、個々のカウントの更新が遅くなります。素晴らしいことではありませんが、私が考慮したいことがあります。 - クリーンなソリューションであり、テーブルで適度に成長することが期待できる他のものはありますか?
更新: 受け入れられた応答の助けを借りて、約 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();