PostgreSQL 9.2のデータベースには、約70のテーブルがあるメインスキーマと、30のテーブルからなるクライアントごとの同一構造の可変数のスキーマがあります。クライアントスキーマには、メインスキーマを参照する外部キーがあり、その逆はありません。
以前のバージョンから取得した実際のデータをデータベースに入力し始めました。メインスキーマの非常に中央のテーブルで一括削除を実行する必要があったときに、DBは約1.5 GB(数週間で数十GBに増加すると予想されています)に達していました。関連するすべての外部キーはON DELETE CASCADEとマークされます。
これに長い時間がかかるのは当然のことでしたが、12時間後には、最初からやり直してDBを削除し、移行を再度開始したほうがよいことが明らかになりました。しかし、DBが稼働していてはるかに大きいときに、後でこの操作を繰り返す必要がある場合はどうなりますか?代替のより高速な方法はありますか?
依存するテーブルを参照するスクリプトを作成し、中央のテーブルから最も遠いテーブルから始めて、テーブルごとに依存する行を削除すると、はるかに速くなりますか?
重要な詳細は、一部のテーブルにトリガーがあることです。
同様の問題がありました。結局のところ、それらのON DELETE CASCADE
トリガーはカスケードされた削除がひどく遅いので、かなり遅くなっていました。
参照元テーブルの外部キーフィールドにインデックスを作成することで問題を解決し、削除に何時間もかかることから数秒になりました。
いくつかのオプションがあります。最良のオプションは、トリガーがヒットしないようにバッチ削除を実行することです。削除する前にトリガーを無効にしてから、再度有効にします。これにより、時間を大幅に節約できます。例えば:
ALTER TABLE tablename DISABLE TRIGGER ALL;
DELETE ...;
ALTER TABLE tablename ENABLE TRIGGER ALL;
ここでの主要なキーは、サブクエリの深さを最小限に抑えることです。この場合、削除時に深いサブクエリを回避できるように、関連情報を格納するために一時テーブルを設定することができます。
問題を解決する最も簡単な方法は、PostgreSQLから詳細なタイミングをクエリすることです EXPLAIN
。そのためには、少なくとも1つのクエリは完了するが、予想よりも時間がかかるクエリを見つける必要があります。この行が次のようになるとしましょう
delete from mydata where id='897b4dde-6a0d-4159-91e6-88e84519e6b6';
実際にそのコマンドを実行する代わりに、あなたは行うことができます
begin;
explain (analyze,buffers,timing) delete from mydata where id='897b4dde-6a0d-4159-91e6-88e84519e6b6';
rollback;
最後にロールバックを使用すると、実際にデータベースを変更せずにこれを実行できますが、どれだけ時間がかかったかの詳細なタイミングを取得できます。それを実行した後、いくつかのトリガーが大きな遅延を引き起こしていることが出力でわかるかもしれません:
...
Trigger for constraint XYZ123: time=12311.292 calls=1
...
time
の単位はミリ秒(ミリ秒)なので、この制約の確認には約12.3秒かかりました。このトリガーを効果的に計算できるように、必要な列に新しいINDEX
を追加する必要があります。外部キー参照の場合、別のテーブルを参照する列にインデックスを付ける必要があります(つまり、ターゲット列ではなくソース列)。 PostgreSQLはそのようなインデックスを自動的に作成しません。DELETE
は、本当にそのインデックスが本当に必要な唯一の一般的なクエリです。その結果、インデックスがないためにDELETE
が遅すぎる場合に到達するまで、何年ものデータが蓄積されている可能性があります。
その制約のパフォーマンスを修正したら(または時間がかかりすぎた場合)、begin
/rollback
ブロックでコマンドを繰り返して、新しい実行時間を以前の実行時間と比較できるようにします。 1行の削除の応答時間が満足できるまで続けます(さまざまなインデックスを追加するだけで、1つのクエリで25.6秒から15ミリ秒になりました)。その後、ハックすることなく完全な削除を完了することができます。
(EXPLAIN
には、正常に完了できるクエリが必要であることに注意してください。以前、PostgreSQLが1つの削除が外部キー制約に違反することを理解するのに非常に時間がかかるという問題がありました。その場合、EXPLAIN
は、失敗したクエリのタイミングを出力しないため使用できません。このような場合のパフォーマンスの問題をデバッグする簡単な方法はわかりません。)
トリガーを無効にすることは、DBの整合性を脅かす可能性があり、推奨できません。ただし、操作が制約失敗防止であると確信している場合は、以下を使用してトリガーを無効にできます。
SET session_replication_role = replica;
ここでDELETE
を実行します。
トリガーを復元するには、次のコマンドを実行します。
SET session_replication_role = DEFAULT;
ON DELETE CASCADEトリガーがある場合、それらは何らかの理由でそこにあるはずなので、無効にしないでください。私にとって有効なもう1つのトリック(まだインデックスを追加します)は、カスケードの最後にあるテーブルからデータを手動で削除し、メインテーブルに向かって機能する削除関数を作成することです。 (これは、ON DELETE RESTRICTトリガーがあった場合と同じです)
CREATE TABLE tablea (
tablea_uid integer
);
CREATE TABLE tableb (
tableb_uid integer,
tablea_rid integer REFERENCES tablea(tablea_uid)
);
CREATE TABLE tablec (
tablec_uid integer,
tableb_rid integer REFERENCES tableb(tableb_uid)
);
この場合、tablec、tableb、tableaのデータを削除します。
CREATE OR REPLACE FUNCTION delete_in_order()
RETURNS void AS $$
DELETE FROM tablec;
DELETE FROM tableb;
DELETE FROM tablea;
$$ LANGUAGE SQL;