Postgresに一括挿入する最も速い方法は何ですか?


241

プログラムで数千万のレコードをpostgresデータベースに挿入する必要があります。現在、1つの「クエリ」で数千の挿入ステートメントを実行しています。

これを行うより良い方法はありますか?私が知らないいくつかの一括挿入ステートメントですか?

回答:


211

PostgreSQLにはデータベースに最初にデータを追加する方法についてのガイドがあり行を一括ロードするためにCOPYコマンドを使用することを推奨しています。このガイドには、データをロードする前にインデックスと外部キーを削除する(そして後で追加する)など、プロセスを高速化する方法に関する他の優れたヒントがいくつかあります。


33
詳細については、stackoverflow.com / questions / 12206600 / …でも詳しく説明しました。
クレイグリンガー

24
@CraigRingerうわー、「もう少し詳細」は、私がこれまでずっと見てきた中で最も控えめな
表現

Install-Package NpgsqlBulkCopyを試してください
Elyor

1
-インデックスは、dbレコードの物理的なレイアウトにも使用されるため。いずれかのデータベースのインデックスを削除することが適切かどうかは不明です。
Farjad

しかし、あなたの推奨、メモリには何もありません!!! そして、あなたのバッチサイズが小さい数になる可能性がある場合、それは非常にうまくいきませんでしたクラス:(私はnpgsql CopyInクラスを試します、それはPGクエリステートメントのCSV形式のマッピングのようなものだからです。あなたは大きなテーブルを試すことができますか?
Elyor

93

Postgresがサポートする複数行の値の構文であるCOPYの使用に代わるものがあります。ドキュメントから:

INSERT INTO films (code, title, did, date_prod, kind) VALUES
    ('B6717', 'Tampopo', 110, '1985-02-10', 'Comedy'),
    ('HG120', 'The Dinner Game', 140, DEFAULT, 'Comedy');

上記のコードは2つの行を挿入しますが、準備されたステートメントトークンの最大数に達するまで、任意に拡張できます($ 999になる可能性がありますが、100%確実ではありません)。時にはCOPYを使用できないことがあり、これはそれらの状況の価値ある置き換えです。


12
この方法のパフォーマンスがCOPYとどのように比較されるか知っていますか?
Grant Humphries、2015

アクセス許可の問題が発生した場合は、これを試す前に、COPY ... FROM STDIN
Andrew Scott Evans

行レベルのセキュリティを使用している場合、これが最善の方法です。バージョン12以降、「行レベルのセキュリティが設定されたテーブルではCOPY FROMはサポートされていません」
Eloff

COPYは拡張INSERTよりもはるかに高速です
ハイパートラッカー

24

速度を上げる1つの方法は、トランザクション内で明示的に複数の挿入またはコピーを実行することです(たとえば1000)。Postgresのデフォルトの動作は、各ステートメントの後にコミットすることです。そのため、コミットをバッチ処理することにより、オーバーヘッドを回避できます。ダニエルの回答のガイドにあるように、これを機能させるには自動コミットを無効にする必要がある場合があります。また、下部にあるコメントで、wal_buffersのサイズを16 MBに増やすと役立つ場合があることにも注意してください。


1
同じトランザクションに追加できる挿入/コピーの数の制限は、試行する何よりもはるかに高い可能性があることに言及する価値があります。同じトランザクション内に数百万行を追加しても問題が発生することはありません。
Sumeet Jain

@SumeetJainはい、私はトランザクションあたりのコピー/挿入の数の点で「スイートスポット」の速度に注目しています。
Dana the Sane

これにより、トランザクションの実行中にテーブルがロックされますか?
ラムダフェアリー

15

UNNEST配列を持つ関数は、複数行のVALUES構文とともに使用できます。私はこの方法は使用するよりも遅いと思いますがCOPY、psycopgとpython(python がpgにlist渡される)を操作するのに役立ちます:cursor.executeARRAY

INSERT INTO tablename (fieldname1, fieldname2, fieldname3)
VALUES (
    UNNEST(ARRAY[1, 2, 3]), 
    UNNEST(ARRAY[100, 200, 300]), 
    UNNEST(ARRAY['a', 'b', 'c'])
);

VALUES追加の存在チェックで副選択を使用しない場合:

INSERT INTO tablename (fieldname1, fieldname2, fieldname3)
SELECT * FROM (
    SELECT UNNEST(ARRAY[1, 2, 3]), 
           UNNEST(ARRAY[100, 200, 300]), 
           UNNEST(ARRAY['a', 'b', 'c'])
) AS temptable
WHERE NOT EXISTS (
    SELECT 1 FROM tablename tt
    WHERE tt.fieldname1=temptable.fieldname1
);

一括更新と同じ構文:

UPDATE tablename
SET fieldname1=temptable.data
FROM (
    SELECT UNNEST(ARRAY[1,2]) AS id,
           UNNEST(ARRAY['a', 'b']) AS data
) AS temptable
WHERE tablename.id=temptable.id;


9

それは主にデータベース内の(その他の)アクティビティに依存します。このような操作は、他のセッションのためにデータベース全体を事実上凍結します。別の考慮事項は、データモデルと、制約、トリガーなどの存在です。

私の最初のアプローチは常に:ターゲットテーブルに似た構造の(一時)テーブルを作成し(テーブルtmp AS select * from target where 1 = 0)、ファイルを一時テーブルに読み込むことから始めます。次に、何をチェックできるかを確認します。重複、ターゲットに既に存在するキーなどです。

次に、「ターゲット選択* tmpから挿入を実行する」などを実行します。

これが失敗した場合、または時間がかかりすぎる場合は、中止して他の方法(一時的にインデックス/制約を削除するなど)を検討します



6

この問題が発生したばかりで、お勧めします 、Postgresへの一括インポートにはcsvsqlreleases)をます。一括挿入を実行するには、データベースに接続し、CSVのフォルダー全体の個別のテーブルを作成するをcreatedb使用csvsqlします。

$ createdb test 
$ csvsql --db postgresql:///test --insert examples/*.csv

1
csvsqlの場合、発生する可能性のあるフォーマットエラーからソースcsvもクリーンアップするために、これらの指示に従うことをお勧めします。詳細については、こちらのドキュメントを参照してください
sal

0

外部ファイルは、最良かつ典型的なバルクデータです

「バルクデータ」という用語は「大量のデータ」に関連しているため、SQLに変換する必要なく、元の生データを使用するのが自然です。「一括挿入」の一般的な生データファイルはCSVJSONです形式形式です。

変換を伴う一括挿入

ETLのアプリケーションや摂取プロセス、我々はそれを挿入する前に、データを変更する必要があります。一時テーブルはディスクスペースを(大量に)消費しますが、それを行うための高速な方法ではありません。PostgreSQLの外部データラッパ(FDW)が最良の選択です。

CSVの例。仮定tablename (x, y, z)SQLとCSVファイルなどに

fieldname1,fieldname2,fieldname3
etc,etc,etc
... million lines ...

従来のSQL COPYを使用して(元のデータと同じように)にロードしtmp_tablename、フィルターされたデータをtablename...に挿入できます。ただし、ディスクの消費を回避するには、

INSERT INTO tablename (x, y, z)
  SELECT f1(fieldname1), f2(fieldname2), f3(fieldname3) -- the transforms 
  FROM tmp_tablename_fdw
  -- WHERE condictions
;

FDW用にデータベースを準備する必要があります。代わりに、静的にそれを生成する関数をtmp_tablename_fdw使用できます。

CREATE EXTENSION file_fdw;
CREATE SERVER import FOREIGN DATA WRAPPER file_fdw;
CREATE FOREIGN TABLE tmp_tablename_fdw(
  ...
) SERVER import OPTIONS ( filename '/tmp/pg_io/file.csv', format 'csv');

JSONの例。2つのファイルのセットでmyRawData1.json、次のRanger_Policies2.json方法で取り込むことができます。

INSERT INTO tablename (fname, metadata, content)
 SELECT fname, meta, j  -- do any data transformation here
 FROM jsonb_read_files('myRawData%.json')
 -- WHERE any_condiction_here
;

ここで、jsonb_read_files()関数は、マスクで定義されたフォルダーのすべてのファイルを読み取ります。

CREATE or replace FUNCTION jsonb_read_files(
  p_flike text, p_fpath text DEFAULT '/tmp/pg_io/'
) RETURNS TABLE (fid int,  fname text, fmeta jsonb, j jsonb) AS $f$
  WITH t AS (
     SELECT (row_number() OVER ())::int id, 
           f as fname,
           p_fpath ||'/'|| f as f
     FROM pg_ls_dir(p_fpath) t(f)
     WHERE    f like p_flike
  ) SELECT id,  fname,
         to_jsonb( pg_stat_file(f) ) || jsonb_build_object('fpath',p_fpath),
         pg_read_file(f)::jsonb
    FROM t
$f$  LANGUAGE SQL IMMUTABLE;

gzipストリーミングの欠如

「ファイルの取り込み」(主にビッグデータ)の最も頻繁な方法は、元のファイルを gzip形式で保持し、それをストリーミングアルゴリズムで転送することです。これは、UNIXパイプでディスクを消費せずに高速で実行できるものです。

 gunzip remote_or_local_file.csv.gz | convert_to_sql | psql 

したがって、理想的な(将来の)は、formatのサーバーオプションです .csv.gz

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.