Postgres'e toplu ekleme yapmanın en hızlı yolu nedir?


242

Programlı olarak 10 milyonlarca kayıt bir postgres veritabanına eklemek gerekir. Şu anda 1000 "ekleme deyimleri tek bir" sorgu "yürütüyorum.

Bunu yapmanın daha iyi bir yolu var mı, bilmediğim bazı toplu ekleme ifadeleri?

Yanıtlar:


211

PostgreSQL, başlangıçta bir veritabanının en iyi nasıl yerleştirileceği konusunda bir kılavuza sahiptir ve toplu yükleme satırları için COPY komutunu kullanmanızı önerir . Kılavuz, verileri yüklemeden önce dizinleri ve yabancı anahtarları kaldırmak (ve daha sonra bunları geri eklemek) gibi işlemin nasıl hızlandırılacağıyla ilgili başka iyi ipuçlarına sahiptir.


33
Stackoverflow.com/questions/12206600/… ' da ayrıntılı bir şekilde daha ayrıntılı bir şekilde yazdım .
Craig Ringer

24
@CraigRinger Vay be, "biraz daha fazla ayrıntı" bütün hafta gördüğüm en iyi
eksikliktir

Install-Package NpgsqlBulkCopy'yi deneyin
Elyor

1
-Bu dizinler de db kayıtlarının fiziksel düzeni için kullanılır. Herhangi bir veritabanındaki dizinleri kaldırmanın iyi bir fikir olup olmadığından emin değilim.
Farjad

Ama önerilen, hiçbir şey bellek !!! Ve parti boyutu küçük sayı olabilir, çok kötü çalıştı sınıf :( I denemek npgsql CopyIn sınıf, çünkü PG sorgu deyimleri CSV biçimli haritalama gibi. Büyük Tablo için deneyebilirsiniz?
Elyor

94

Postgres'in desteklediği çok satırlı değerler sözdizimi olan COPY'yi kullanmanın bir alternatifi vardır. Gönderen belgeler :

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

Yukarıdaki kod iki satır ekler, ancak hazırlanan deyim jetonlarının maksimum sayısına ulaşana kadar keyfi olarak genişletebilirsiniz (999 $ olabilir, ancak bundan% 100 emin değilim). Bazen COPY kullanılamaz ve bu durumların yerini almaya değer.


12
Bu yöntemin performansının COPY ile nasıl karşılaştırıldığını biliyor musunuz?
Grant Humphries

Bir izin sorunu ile karşılaşırsanız, bunu denemeden önce, KOPYALAMA ... STDIN'DEN kullanın
Andrew Scott Evans

Satır düzeyinde güvenlik kullanıyorsanız, yapabileceğiniz en iyi şey budur. Sürüm 12'den itibaren "FROM FROM satır düzeyinde güvenliği olan tablolar için desteklenmez"
Eloff

COPY uzatılmış
INSERT'ten

24

İşleri hızlandırmanın bir yolu, bir işlem içinde açıkça birden fazla ek veya kopya gerçekleştirmektir (örneğin 1000). Postgres'in varsayılan davranışı her ifadeden sonra işlem yapmaktır, bu nedenle taahhütleri toplu hale getirerek bazı ek yükleri önleyebilirsiniz. Daniel'in cevabındaki kılavuzda belirtildiği gibi, bunun çalışması için otomatik taahhüdü devre dışı bırakmanız gerekebilir. Ayrıca, wal_buffers'ın boyutunu 16 MB'a çıkarmayı öneren altta yer alan yorumun da yardımcı olabileceğini unutmayın.


1
Aynı işleme ne kadar ek / kopya ekleyebileceğiniz sınırının, denediğiniz her şeyden çok daha yüksek olduğunu belirtmek gerekir. Aynı işleme milyonlarca ve milyonlarca satır ekleyebilir ve sorun yaşamayabilirsiniz.
Sumeet Jain

@SumeetJain Evet, işlem başına kopya / ek sayısı açısından 'tatlı nokta' hızına dikkat çekiyorum.
Dana the Sane

Bu işlem çalışırken tabloyu kilitleyecek mi?
Lambda Fairy

15

UNNESTdizilerle işlev, çok satırlı VALUES sözdizimi ile birlikte kullanılabilir. Bu yöntem kullanmaktan daha yavaş olduğunu düşünüyorum COPYama psycopg ve python (python listgeçti cursor.executepg ARRAY) ile çalışmalarında benim için yararlı :

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

olmadan VALUESek varlığı çek ile Alt Seç'i kullanarak:

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

toplu güncellemelerle aynı sözdizimi:

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

Çoğunlukla veritabanındaki (diğer) etkinliğe bağlıdır. Bunun gibi işlemler, diğer oturumlar için tüm veritabanını etkili bir şekilde dondurur. Başka bir husus, veri modeli ve kısıtlamaların, tetikleyicilerin vb.

İlk yaklaşımım her zaman: hedef tabloya benzer bir yapıya sahip bir (temp) tablo oluşturmak (1 = 0 olan hedeften tablo tmp AS select * oluştur) ve dosyayı geçici tabloya okuyarak başlayın. Sonra neyin kontrol edilebileceğini kontrol ediyorum: kopyalar, hedefte zaten var olan anahtarlar, vb.

Sonra ben sadece "tmp from target select * eklemek" veya benzeri yapmak.

Bu başarısız olursa veya çok uzun sürerse, iptal ediyorum ve diğer yöntemleri (geçici olarak dizinleri / kısıtlamaları vb.



6

Ben sadece bu sorunla karşılaştı ve Postgres toplu ithalat için csvsql ( bültenleri ) tavsiye ederim . Toplu bir ekleme yapmak için, veritabanınıza bağlanan ve tüm CSV klasörleri için ayrı ayrı tablolar oluşturan basit createdbve daha sonra kullanabilirsiniz csvsql.

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

1
Csvsql için, kaynak csv'yi olası biçimlendirme hatalarından da temizlemek için, bu talimatları , daha fazla belgeyi burada
sal

0

Harici dosya en iyi ve tipik toplu veridir

"Toplu veri" terimi "çok fazla veri" ile ilgilidir, bu nedenle orijinal ham verilerin SQL'e dönüştürülmesine gerek kalmadan kullanılması doğaldır . "Toplu ekleme" için tipik ham veri dosyaları CSV ve JSON formatlarıdır.

Biraz dönüşümle birlikte toplu ekleme

Gelen ETL uygulamaları ve yutma süreçler, bunu takmadan önce verileri değiştirmek gerekir. Geçici tablo (çok fazla) disk alanı tüketir ve bunu yapmanın daha hızlı yolu değildir. PostgreSQL yabancı veri sarıcı (FDW) en iyi seçimdir.

CSV örneği . Varsayalım tablename (x, y, z)SQL ve bir CSV dosyası gibi üzerinde

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

Sen klasik SQL kullanabilirsiniz COPY(yüke olduğu gibi orijinal veri) tmp_tablenameiçine, onları filtreden insert veri tablenameönlemek diski tüketimine ... Ama iyi tarafından doğrudan ağızdan etmektir

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

FDW için veritabanı hazırlamanız gerekir ve bunun yerine statik tmp_tablename_fdw, onu üreten bir işlevi kullanabilirsiniz :

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 örneği . Bir dizi iki dosya myRawData1.jsonve Ranger_Policies2.jsontarafından alınabilir:

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
;

burada jsonb_read_files () işlevi , bir klasörün maske tarafından tanımlanan tüm dosyalarını okur:

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 akışının olmaması

"Dosya alımı" için en sık kullanılan yöntem (esas olarak Büyük Veri'de) orijinal dosyayı gzip biçiminde korumak ve dosyayı unix borularda hızlı ve disk tüketimi olmadan çalışabilen akış algoritmasıyla aktarmaktır :

 gunzip remote_or_local_file.csv.gz | convert_to_sql | psql 

Yani ideal (gelecek) format için bir sunucu seçeneğidir.csv.gz .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.