web-dev-qa-db-ja.com

一時テーブルを使用した効率的なPostgreSQLの更新

製品詳細の更新をファイルからインポートする小さなプログラムを書いたのですが、予想以上に時間がかかります。 (簡潔にするために、簡略化した例を使用します。)

プログラムは次のことを行います。

  1. ファイルからデータを読み込みます。
  2. 特定の変更を実行し、メモリ内ファイルを作成します。
  3. 処理されたファイルデータを保持する一時テーブルを作成します。
  4. COPYs変更されたデータを一時テーブルに入れます。
  5. 一時テーブルから実際のテーブルを更新します。

これはすべて正常に機能しますが、UPDATEクエリは、〜2000行の小さなファイルに対して〜20秒かかります。

一時テーブルは次のようになります。

CREATE TEMPORARY TABLE tmp_products (
  product_id integer,
  detail text
);

そして、私の更新クエリは本当に簡単です:

UPDATE products
SET detail = t.detail
FROM tmp_products t
WHERE t.product_id = products.product_id

物事をスピードアップするために、私はほとんど成功せずに以下を試しました:

一時テーブルにBTREEインデックスを作成します。

CREATE INDEX tmp_products_idx
  ON tmp_products
  USING BTREE
  (product_id);

HASHインデックスの作成:

CREATE INDEX tmp_products_idx
  ON tmp_products
  USING HASH
  (product_id);

どちらのインデックスも更新時間を大幅に改善しませんでした。次に、テーブルをクラスタ化すると役立つと思いましたが、そのため、HASHインデックスを使用できませんでした。そこで、BTREEインデックスを使用してCLUSTER/ANALYZEを使用するようにプログラムのクエリを変更しました。

CREATE INDEX tmp_products_idx
  ON tmp_products
  USING BTREE
  (product_id);

-- Program inserts data

CLUSTER tmp_products USING tmp_products_idx;
ANALYZE tmp_products;

これも何の助けにもなりませんでした。 CLUSTERがBTREEを使用し、UPDATEがHASHを使用することを期待して、BTREEインデックスとHASHインデックスの両方を使用して、もう1度試してみました。

CREATE INDEX tmp_products_btree_idx
  ON tmp_products
  USING BTREE
  (product_id);

CREATE INDEX tmp_products_hash_idx
  ON tmp_products
  USING BTREE
  (product_id);

-- Program inserts data

CLUSTER tmp_products USING tmp_products_btree_idx;
ANALYZE tmp_products;

そして再び、何も助けにはなりませんでした。中断したところ、私はまだ正しい-2000行で20秒。私の職場では通常20秒の更新が許容されますが、2000行のファイルは小さなサンプルテストに使用しています。大きいファイルは時間がかかりすぎます。

productsテーブルに関するいくつかの詳細(重要な場合):

行:〜630k
列:54
インデックス:19
トリガー:14
テーブルサイズ:〜1.2GB
インデックスサイズ:〜2.2GB

ボトルネックが1つ以上のトリガーにあると強く思われますが、それらのトリガーを削除または変更することはできません。アップデートの効率を向上させるために何かできることはありますか?

2
That1Guy

できること、できるかどうかは別問題です。

  1. あなたのUPDATEが非常に単純であることを考えると、私の最初の推測は、あなたのトリガーがパフォーマンスを低下させていることです。トリガーの処理は通常、特に解釈された言語で記述されている場合(つまり、多かれ少なかれCで記述されていない場合)、時間がかかります。 開発マシンでチェックする可能性がある場合は、[allトリガーの無効化をテストして、それがどのような影響を与えるかを確認してください(クエリの時間です!)。次に、それらを1つずつ再度有効にし、それぞれがタイミングに与える影響を確認します。あなたは見つけることができますいくつか痛むトリガー(たくさん)。 alotの時間を消費するものがある場合は、資格のある人に修正してもらい、最適化し、必要に応じてCを使用して書き直してもらいます。私の経験では、あらゆる種類のloggingまたはauditing挿入すると、プロセスが(簡単に)10倍遅くなる可能性があります。14のトリガーに加えて、データベースが確認のために追加した可能性があることを考慮してくださいすべての制約が満たされます(CHECKREFERENCESUNIQUE、...)。それらを無効にしようとすることは、通常は良い考えではありません(そして、可能であれば、それを行うのは簡単ではありません)。

  2. セットアップにすべてのインデックスが本当に必要かどうかを確認してください。 PostgreSQL wikiの Unused Indexes に関する説明を確認してください。クエリの動作方法(列detailのみを更新します)、detailanyインデックスの一部でない場合、これはあまり影響しません。 PostgreSQL すべきヒープのみのタプル(HOT) 更新を実行でき、インデックスは大きな影響を与えません。

  3. HOTの更新を成功させるには、テーブルにfreeスペースが必要です。したがって、テーブルのfillfactorが100未満であることを確認してください。 docsからCREATE TABLE

    fillfactor (integer)

    テーブルのfillfactorは、10から100の間のパーセントです。100(完全なパッキング)がデフォルトです。より小さいfillfactorが指定されている場合、INSERT操作は、指定されたパーセンテージにのみテーブルページをパックします。各ページの残りのスペースは、そのページの行を更新するために予約されています。 これにより、UPDATEは行の更新されたコピーを元のページと同じページに配置する機会が得られます。これは、別のページに配置するよりも効率的です。エントリが決して更新されないテーブルの場合、完全なパッキングが最良の選択ですが、頻繁に更新されるテーブルでは、より小さいフィルファクターが適切です。このパラメーターはTOASTテーブルには設定できません。

    (強調鉱山)

  4. 一時テーブルにcovering indexを設定することを検討してください。あれは:

    CREATE INDEX tmp_products_idx
    ON tmp_products
    USING BTREE
    (product_id, detail);
    
    ANALYZE tmp_products;
    

    これは、detailの長さが中程度の場合にのみ意味があります。 インデックスのみのスキャンソース更新の一部ですが、試してみないと確信が持てません。

より詳細なアドバイスを提供するには、実行計画に関する詳細情報が必要になります。

3
joanolo

大量のデータを挿入/更新する必要がある場合、データテーブルには1つのlifeHackがあります。このテーブルのインデックスを無効化/削除する必要があり、挿入操作の後、インデックスを有効化または再作成するだけです。これははるかに速くなります。 PS遅くなって申し訳ありませんが、2年の回答がありますO_o

0
Max Sherbakov