Apa cara tercepat untuk melakukan penyisipan massal ke Postgres?

242

Saya perlu secara program memasukkan 10 dari jutaan catatan ke dalam database postgres. Saat ini saya sedang mengeksekusi 1000 pernyataan memasukkan dalam satu "permintaan".

Apakah ada cara yang lebih baik untuk melakukan ini, beberapa pernyataan penyisipan massal yang tidak saya ketahui?

Abu
sumber

Jawaban:

211

PostgreSQL memiliki panduan tentang cara terbaik mengisi database pada awalnya, dan mereka menyarankan menggunakan perintah COPY untuk baris pemuatan massal. Panduan ini memiliki beberapa tips bagus tentang cara mempercepat proses, seperti menghapus indeks dan kunci asing sebelum memuat data (dan menambahkannya kembali setelah itu).

Dan Lew
sumber
33
Saya menulis sedikit lebih detail untuk diuraikan di stackoverflow.com/questions/12206600/... juga.
Craig Ringer
24
@CraigRinger Wow, "sedikit lebih detail" adalah pernyataan terbaik yang pernah saya lihat sepanjang minggu;)
culix
Coba Instal-Paket NpgsqlBulkCopy
Elyor
1
-Karena indeks juga digunakan untuk tata letak fisik catatan db. Tidak yakin apakah menghapus indeks di basis data apa pun adalah ide yang bagus.
Farjad
Tapi yang Anda rekomendasikan, tidak ada dalam Memori !!! Dan jika ukuran batch Anda bisa berjumlah kecil, sangat-sangat buruk bekerja itu kelas :( Saya Coba kelas CopyIn npgsql, karena itu seperti pemetaan CSV yang diformat dalam pernyataan kueri PG. Anda dapat mencoba untuk Big Table?
Elyor
94

Ada alternatif untuk menggunakan COPY, yang merupakan sintaks nilai multirow yang didukung Postgres. Dari dokumentasi :

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

Kode di atas menyisipkan dua baris, tetapi Anda dapat memperpanjangnya secara sewenang-wenang, sampai Anda menekan jumlah maksimum token pernyataan yang disiapkan (mungkin $ 999, tapi saya tidak 100% yakin tentang itu). Terkadang seseorang tidak dapat menggunakan COPY, dan ini adalah pengganti yang layak untuk situasi tersebut.

Ben Harper
sumber
12
Apakah Anda tahu bagaimana kinerja metode ini dibandingkan dengan COPY?
Grant Humphries
Jika Anda mengalami masalah izin, sebelum mencoba ini, gunakan COPY ... FROM STDIN
Andrew Scott Evans
Jika Anda menggunakan keamanan tingkat baris, ini adalah yang terbaik yang dapat Anda lakukan. "COPY FROM tidak didukung untuk tabel dengan keamanan tingkat baris" pada versi 12.
Eloff
COPY jauh lebih cepat daripada INSERT yang diperluas
hipertracker
24

Salah satu cara untuk mempercepatnya adalah dengan secara eksplisit melakukan beberapa sisipan atau salinan dalam suatu transaksi (katakanlah 1000). Perilaku default Postgres adalah melakukan setelah setiap pernyataan, jadi dengan mengelompokkan komit, Anda dapat menghindari beberapa overhead. Seperti panduan dalam jawaban Daniel mengatakan, Anda mungkin harus menonaktifkan autocommit agar ini berfungsi. Perhatikan juga komentar di bagian bawah yang menunjukkan peningkatan ukuran wal_buffers menjadi 16 MB juga dapat membantu.

Dana si Sane
sumber
1
Perlu disebutkan bahwa batas untuk berapa banyak sisipan / salinan yang dapat Anda tambahkan ke transaksi yang sama kemungkinan jauh lebih tinggi daripada apa pun yang akan Anda coba. Anda bisa menambahkan jutaan dan jutaan baris dalam transaksi yang sama dan tidak mengalami masalah.
Sumeet Jain
@SumeetJain Ya, saya hanya berkomentar pada kecepatan 'sweet spot' dalam hal jumlah salinan / sisipan per transaksi.
Dana the Sane
Apakah ini akan mengunci tabel saat transaksi berjalan?
Lambda Fairy
15

UNNESTfungsi dengan array dapat digunakan bersama dengan sintaks VALUES multirow. Saya pikir metode ini lebih lambat daripada menggunakan COPYtetapi berguna bagi saya dalam bekerja dengan psycopg dan python (python listdilewatkan cursor.executemenjadi pg ARRAY):

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

tanpa VALUESmenggunakan subselect dengan pemeriksaan keberadaan tambahan:

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
);

sintaksis yang sama untuk pembaruan massal:

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;
ndpu
sumber
9

Sebagian besar tergantung pada aktivitas (lainnya) dalam database. Operasi seperti ini secara efektif membekukan seluruh basis data untuk sesi lain. Pertimbangan lain adalah model data dan adanya kendala, pemicu, dll.

Pendekatan pertama saya adalah selalu: membuat tabel (temp) dengan struktur yang mirip dengan tabel target (buat tabel tmp AS pilih * dari target di mana 1 = 0), dan mulai dengan membaca file ke dalam tabel temp. Lalu saya periksa apa yang bisa diperiksa: duplikat, kunci yang sudah ada di target, dll.

Lalu saya hanya melakukan "jangan masukkan ke target pilih * dari tmp" atau serupa.

Jika ini gagal, atau terlalu lama, saya batalkan dan pertimbangkan metode lain (sementara menjatuhkan indeks / kendala, dll)

wildplasser
sumber
6

Saya baru saja mengalami masalah ini dan akan merekomendasikan csvsql ( rilis ) untuk impor massal ke Postgres. Untuk melakukan penyisipan massal Anda cukup createdbdan kemudian menggunakan csvsql, yang menghubungkan ke database Anda dan membuat tabel individual untuk seluruh folder CSV.

$ createdb test 
$ csvsql --db postgresql:///test --insert examples/*.csv
Sarah Frostenson
sumber
1
Untuk csvsql, untuk juga membersihkan sumber csv dari kemungkinan kesalahan pemformatan, yang terbaik adalah mengikuti petunjuk ini , dokumentasi lebih lanjut di sini
sal
0

File eksternal adalah data massal terbaik dan tipikal

Istilah "data massal" terkait dengan "banyak data", jadi wajar untuk menggunakan data mentah asli , tanpa perlu mengubahnya menjadi SQL. File data mentah yang umum untuk "sisipan massal" adalah format CSV dan JSON .

Sisipkan massal dengan beberapa transformasi

Dalam aplikasi ETL dan proses menelan, kita perlu mengubah data sebelum memasukkannya. Tabel sementara mengkonsumsi (banyak) ruang disk, dan itu bukan cara yang lebih cepat untuk melakukannya. The PostgreSQL asing-data yang wrapper (FDW) adalah pilihan terbaik.

Contoh CSV . Misalkan tablename (x, y, z)pada SQL dan file CSV suka

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

Anda dapat menggunakan SQL klasik COPYuntuk memuat ( seperti data asli) ke dalam tmp_tablename, mereka memasukkan data yang disaring ke tablename... Tapi, untuk menghindari konsumsi disk, yang terbaik adalah dengan langsung dicerna oleh

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

Anda perlu menyiapkan basis data untuk PLRT Asing, dan sebaliknya statis tmp_tablename_fdwAnda bisa menggunakan fungsi yang menghasilkannya :

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');

Contoh JSON . Seperangkat dua file, myRawData1.jsondan Ranger_Policies2.jsondapat dicerna dengan:

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
;

di mana fungsi jsonb_read_files () membaca semua file folder, ditentukan oleh mask:

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;

Kurangnya streaming gzip

Metode yang paling sering untuk "konsumsi file" (terutama di Big Data) adalah mempertahankan file asli pada format gzip dan mentransfernya dengan algoritma streaming , apa pun yang dapat berjalan cepat dan tanpa konsumsi diska di pipa unix:

 gunzip remote_or_local_file.csv.gz | convert_to_sql | psql 

Jadi ideal (masa depan) adalah opsi server untuk format .csv.gz.

Peter Krauss
sumber