web-dev-qa-db-ja.com

IDで数百万行を削除する最良の方法

PGデータベースから約200万行を削除する必要があります。削除する必要があるIDのリストがあります。ただし、これを実行しようとすると、何日もかかります。

それらをテーブルに入れて、100のバッチで実行してみました。4日後、297268行のみが削除された状態で実行されています。 (IDテーブルから100個のIDを選択し、そのリストのどこから削除し、選択した100個のIDテーブルから削除しなければなりませんでした)。

私は試した:

DELETE FROM tbl WHERE id IN (select * from ids)

それも永遠にかかっています。完了するまで進行状況を確認できないため、どのくらいの期間を測定するのは難しいですが、クエリは2日後も実行されていました。

削除する特定のIDがわかっていて、数百万のIDがある場合に、テーブルから削除する最も効果的な方法を探しているだけです。

62
Anthony Greco

それはすべて依存しています...

  • すべてのインデックスを削除します(削除に必要なIDのインデックスを除く)
    後で再作成します(=インデックスの増分更新よりもはるかに高速です)

  • 一時的に安全に削除/無効化できるトリガーがあるかどうかを確認します

  • 外部キーはテーブルを参照しますか?削除できますか?一時的に削除しますか?

  • Autovacuumの設定に応じて、mayを実行すると役立つVACUUM ANALYZE操作前。

  • no no write write accessが関係するテーブルであるか、テーブルを排他的にロックする必要がある場合、またはこのルートがまったく使用できない場合があります。

  • マニュアルの関連する章にリストされているポイントのいくつか データベースへの移入 も、設定によっては役に立つ場合があります。

  • テーブルの大部分を削除し、残りがRAMに収まる場合、最も速くて簡単な方法は次のとおりです。

SET temp_buffers = '1000MB'; -- or whatever you can spare temporarily

CREATE TEMP TABLE tmp AS
SELECT t.*
FROM   tbl t
LEFT   JOIN del_list d USING (id)
WHERE  d.id IS NULL;      -- copy surviving rows into temporary table

TRUNCATE tbl;             -- empty table - truncate is very fast for big tables

INSERT INTO tbl
SELECT * FROM tmp;        -- insert back surviving rows.

この方法では、ビュー、外部キー、またはその他の依存オブジェクトを再作成する必要はありません。 temp_buffersマニュアルの設定 。この方法は、テーブルがメモリに収まる限り、または少なくともそのほとんどが高速です。この操作の途中でサーバーがクラッシュすると、データを失う可能性があることに注意してください。すべてをトランザクションにラップして、安全性を高めることができます。

その後、ANALYZEを実行します。またはVACUUM ANALYZE切り捨てルートに行かなかった場合、またはVACUUM FULL ANALYZE最小サイズにしたい場合。大きなテーブルの場合は、CLUSTER/pg_repack

小さいテーブルの場合、DELETEの代わりに単純なTRUNCATEの方が高速になることがよくあります。

DELETE FROM tbl t
USING  del_list d
WHERE  t.id = d.id;

読み取りマニュアルのTRUNCATEのセクション 。特に( Pedroも彼のコメントで指摘したように ):

TRUNCATEは、他のテーブルからの外部キー参照を持つテーブルでは使用できません。ただし、そのようなテーブルもすべて同じコマンドで切り捨てられます。 [...]

そして:

TRUNCATEON DELETEテーブルに存在する可能性のあるトリガー。

80

PostgreSQLの更新/削除のパフォーマンスは、Oracleほど強力ではないことがわかっています。数百万行または数千万行を削除する必要がある場合、それは非常に難しく、時間がかかります。

ただし、実稼働データベースではこれを行うことができます。私のアイデアは次のとおりです。

最初に、2つの列を持つログテーブルを作成する必要があります-idflagidは削除するIDを指し、flagYまたはnull(レコードが正常に削除されたことを示すY付き)。

後で、関数を作成します。 10,000行ごとに削除タスクを実行します。 私のブログ で詳細を見ることができます。中国語ですが、そこのSQLコードから必要な情報を取得できます。

両方のテーブルのid列がより高速に実行されるため、インデックスであることを確認してください。

4
francs

テーブル内のすべてのデータをコピーしてみてください除く削除したいIDを新しいテーブルに追加し、名前を変更してからテーブルを交換します(十分なリソースがある場合)。

これは専門家のアドバイスではありません。

2つの可能な答え:

  1. レコードを削除しようとすると、テーブルに多くの制約またはトリガーが関連付けられている場合があります。多くのプロセッササイクルと他のテーブルからのチェックが発生します。

  2. このステートメントをトランザクション内に配置する必要がある場合があります。

2
Zaldy Baguinon

最初に、削除するテーブルと削除IDに使用しているテーブルの両方のIDフィールドにインデックスがあることを確認します。

一度に100個は小さすぎるようです。 1000または10000を試してください。

削除IDテーブルから何かを削除する必要はありません。バッチ番号に新しい列を追加し、バッチ1に1000、バッチ2に1000などを入力し、削除クエリにバッチ番号が含まれていることを確認します。

2
Mark Ransom

これを行う最も簡単な方法は、すべての制約を削除してから削除することです。

1
Vincent Agnello

削除するテーブルが_some_other_table_で参照されている場合(そして一時的にも外部キーを削除したくない場合)、referencing列にインデックスがあることを確認してください_some_other_table_!

同様の問題があり、_auto_explain_と_auto_explain.log_nested_statements = true_を使用しました。これにより、deleteが実際に_some_other_table_でseq_scansを実行していることがわかりました。

_    Query Text: SELECT 1 FROM ONLY "public"."some_other_table" x WHERE $1 OPERATOR(pg_catalog.=) "id" FOR KEY SHARE OF x    
    LockRows  (cost=[...])  
      ->  Seq Scan on some_other_table x  (cost=[...])  
            Filter: ($1 = id)
_

どうやら他のテーブルの参照行をロックしようとしているようです(存在しないはずです。そうしないと、削除に失敗します)。参照するテーブルにインデックスを作成した後、削除は桁違いに速くなりました。

0
FunctorSalad