Postgres データベースに数千万のレコードをプログラムで挿入する必要があります。現在、1 つのクエリで何千もの挿入ステートメントを実行しています。
これを行うためのより良い方法はありますか、私が知らない一括挿入ステートメントはありますか?
Postgres データベースに数千万のレコードをプログラムで挿入する必要があります。現在、1 つのクエリで何千もの挿入ステートメントを実行しています。
これを行うためのより良い方法はありますか、私が知らない一括挿入ステートメントはありますか?
高速化する 1 つの方法は、トランザクション内で複数の挿入またはコピーを明示的に実行することです (たとえば、1000)。Postgres のデフォルトの動作は、各ステートメントの後にコミットすることです。そのため、コミットをバッチ処理することで、オーバーヘッドをいくらか回避できます。ダニエルの回答のガイドにあるように、これを機能させるには自動コミットを無効にする必要がある場合があります。また、wal_buffers のサイズを 16 MB に増やすことを提案する下部のコメントにも注意してください。
「テキスト形式や CSV 形式よりも若干高速COPY table TO ... WITH BINARY
」な方をご利用いただけます。これは、挿入する行が数百万あり、バイナリ データに慣れている場合にのみ行ってください。
バイナリ入力で psycopg2 を使用した Python のレシピの例を次に示します。
それは主に、データベース内の (その他の) アクティビティに依存します。このような操作は、他のセッションのためにデータベース全体を効果的にフリーズします。別の考慮事項は、データモデルと、制約、トリガーなどの存在です。
私の最初のアプローチは常に: ターゲット テーブル ( ) に似た構造を持つ (一時) テーブルを作成しcreate table tmp AS select * from target where 1=0
、ファイルを一時テーブルに読み込むことから始めます。次に、チェックできるものをチェックします: 重複、ターゲットに既に存在するキーなど。
次に、do insert into target select * from tmp
または同様のことを行います。
これが失敗した場合、または時間がかかりすぎる場合は、中止して他の方法を検討します (一時的にインデックス/制約を削除するなど)。
((これは編集して回答を強化できる WIKI です!))
「バルクデータ」という用語は「大量のデータ」に関連しているため、SQL に変換する必要がなく、元の生データを使用するのが自然です。「一括挿入」の一般的な生データ ファイルは、CSVおよびJSON形式です。
ETLアプリケーションと取り込みプロセスでは、挿入する前にデータを変更する必要があります。一時テーブルは (大量の) ディスク領域を消費しますが、これは高速な方法ではありません。PostgreSQL 外部データ ラッパー( FDW) が最適です。
CSV の例。on 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 形式で保存し、ストリーミング アルゴリズムを使用して転送することです。これは、Unix パイプでディスクを消費せずに高速で実行できるものなら何でも:
gunzip remote_or_local_file.csv.gz | convert_to_sql | psql
したがって、理想的な(将来)は、format のサーバーオプション.csv.gz
です。
@CharlieClark コメントの後の注意: 現在 (2022) 何もする必要はありません。最良の代替手段はpgloader
STDINのようです:
gunzip -c file.csv.gz | pgloader --type csv ... - pgsql:///target?foo
この問題に遭遇したばかりで、Postgres への一括インポートにはcsvsql ( releases ) をお勧めします。一括挿入を実行するには、単純createdb
に を使用csvsql
して、データベースに接続し、CSV のフォルダー全体に対して個々のテーブルを作成します。
$ createdb test
$ csvsql --db postgresql:///test --insert examples/*.csv