web-dev-qa-db-ja.com

Postgresに一括挿入を行う最速の方法は何ですか?

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

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

200
Ash

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

182
Daniel Lew

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

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を使用できない場合もありますが、これはこれらの状況に代わる価値のあるものです。

70
Ben Harper

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

19
Dana the Sane

UNNEST 配列を持つ関数は、複数行のVALUES構文とともに使用できます。このメソッドはCOPYを使用するよりも遅いと思いますが、psycopgとpythonを使用する場合に役立ちます(cursor.executeに渡されるpython listはpg ARRAYになります)。

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;
10
ndpu

COPY table TO ... WITH BINARYを使用できます。これは「 テキストおよびCSV形式よりもやや速い 」です。これは、挿入する数百万の行があり、バイナリデータに慣れている場合にのみ行ってください。

Pythonのサンプルレシピ、バイナリ入力でpsycopg2を使用 です。

9
Mike T

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

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

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

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

7
wildplasser

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

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

ネイティブのlibpqメソッドを使用して、非常に高速なPostgresqデータローダーを実装しました。パッケージを試してみてください https://www.nuget.org/packages/NpgsqlBulkCopy/

5
Elyor