web-dev-qa-db-ja.com

大規模なデータベーステーブルからの削除を高速化するにはどうすればよいですか?

私が解決しようとしている問題は次のとおりです。最近、データベースを複数のシャード間で負荷分散できるようにするデータレイヤーの再設計を完了しました。シャードのバランスを保つために、あるシャードから別のシャードにデータを移行できる必要があります。これには、シャードAからシャードBにコピーしてから、シャードAからレコードを削除する必要があります。しかし、非常に大きなテーブルがいくつかあります。また、多くの外部キーがそれらを指しているため、テーブルから1つのレコードを削除するには1秒以上かかる場合があります。

場合によっては、テーブルから何百万ものレコードを削除する必要があり、実用的であるには時間がかかりすぎます。

外部キーを無効にすることはオプションではありません。行の大きなバッチを削除することもオプションではありません。これは実稼働アプリケーションであり、大きな削除は多くのリソースをロックし、失敗を引き起こすためです。私はSQLServerを使用しており、パーティションテーブルについては知っていますが、パーティションの制限(およびEnterprise Editionのライセンス料)は非現実的であるため、不可能です。

この問題に取り組み始めたとき、難しい部分は、リーフレベルからデータモデルの最上位までの行を削除する方法を理解するアルゴリズムを記述して、途中で外部キーの制約に違反しないようにすることだと思いました。しかし、その問題を解決しても、一晩で消える必要のあるレコードを削除するのに数週間かかるため、私は役に立たなかった。

私はすでにデータを仮想的に削除されたものとしてマークする方法を構築しました。アプリケーションに関する限り、データは失われますが、サイズが非常に大きいため、大きなデータファイル、大きなバックアップ、および遅いクエリを処理しています。テーブル。

何か案は?私はすでにここで古い関連記事を読みましたが、役立つものは何も見つかりませんでした。

24
Eric Z Beard

参照してください: SQL Serverでの削除の最適化

このMSサポート記事は興味深いかもしれません: SQL Serverのロックエスカレーションによって引き起こされるブロッキングの問題を解決する方法

大きなバッチ操作をいくつかの小さな操作に分割します。たとえば、次のクエリを実行して監査テーブルから数十万の古いレコードを削除した後、他のユーザーをブロックするロックエスカレーションが発生したとします。

DELETE FROM LogMessages WHERE LogDate < '2/1/2002'    

これらのレコードを一度に数百個削除することで、トランザクションごとに蓄積されるロックの数を大幅に減らし、ロックのエスカレーションを防ぐことができます。例えば:

SET ROWCOUNT 500
delete_more:
     DELETE FROM LogMessages WHERE LogDate < '2/1/2002'
IF @@ROWCOUNT > 0 GOTO delete_more
SET ROWCOUNT 0

クエリを可能な限り効率的にすることで、クエリのロックフットプリントを削減します。大量のスキャンまたは多数のブックマークルックアップは、ロックエスカレーションの可能性を高める可能性があります。さらに、デッドロックの可能性が高くなり、一般に同時実行性とパフォーマンスに悪影響を及ぼします。

28
Mitch Wheat
delete_more:
     DELETE TOP(500) FROM LogMessages WHERE LogDate < '2/1/2002'
IF @@ROWCOUNT > 0 GOTO delete_more

Mitchが提案したSET ROWCOUNTを使用しても同じ結果を得ることができますが、 MSDNによるDELETEおよびSQLServerの将来のバージョンでのその他の操作ではサポートされません。 :

SET ROWCOUNTを使用しても、SQL Serverの将来のリリースでは、DELETE、INSERT、およびUPDATEステートメントには影響しません。新しい開発作業では、DELETE、INSERT、およびUPDATEステートメントでSET ROWCOUNTを使用することは避け、現在それを使用しているアプリケーションを変更することを計画してください。同様の動作については、TOP構文を使用してください。詳細については、TOP(Transact-SQL)を参照してください。

17
foobarcode

新しいファイルを作成し、「削除された」行を除くすべてをコピーしてから、テーブルの名前を入れ替えることができます。最後に、古いテーブルを削除します。レコードの大部分を削除する場合、これは実際には高速である可能性があります。

1
seanyboy

もう1つの提案は、テーブルの名前を変更し、ステータス列を追加することです。 status = 1(削除済み)の場合、表示したくありません。したがって、ステータスがnullまたは= 0の場合にテーブルから選択する元のテーブルと同じ名前のビューを作成します(実装方法によって異なります)。削除はユーザーにすぐに表示され、バックグラウンドジョブは15分ごとに実行され、dbas以外の誰もが気付かないうちに実行されたレコードを削除できます。

1
HLGEM

次のように、whileループを使用して小さなバッチを削除できます。

DELETE TOP (10000) FROM LogMessages WHERE LogDate < '2/1/2002'
WHILE @@ROWCOUNT > 0
BEGIN
    DELETE TOP (10000) FROM LogMessages WHERE LogDate < '2/1/2002'
END
0

SQL 2005または2008を使用している場合は、「スナップショットアイソレーション」を使用すると役立つ可能性があります。これにより、基になるデータ更新操作の処理が行われている間、データがユーザーに表示されたままになり、コミットされるとすぐにデータが表示されます。削除の実行に30分かかる場合でも、この間、アプリケーションはオンラインのままになります。

スナップショットロックの簡単な入門書は次のとおりです。

http://www.mssqltips.com/tip.asp?tip=1081

削除をできるだけ速くするために削除を高速化する必要がありますが、これにより負担が軽減される場合があります。

0
SqlRyan