Qual è il modo più veloce per eseguire un inserimento in blocco in Postgres?


242

Ho bisogno di inserire programmaticamente decine di milioni di record in un database Postgres. Attualmente sto eseguendo migliaia di istruzioni insert in una singola "query".

C'è un modo migliore per farlo, qualche dichiarazione di inserimento in blocco che non conosco?

Risposte:


211

PostgreSQL ha una guida su come popolare inizialmente un database e suggeriscono di usare il comando COPY per il caricamento di file di massa. La guida ha alcuni altri buoni consigli su come velocizzare il processo, come la rimozione di indici e chiavi esterne prima di caricare i dati (e aggiungerli successivamente).


33
Ho scritto un po 'più di dettagli da elaborare anche in stackoverflow.com/questions/12206600/… .
Craig Ringer,

24
@CraigRinger Wow, "un po 'più di dettaglio" è il miglior eufemismo che abbia mai visto tutta la settimana;)
culmine

Prova il pacchetto di installazione NpgsqlBulkCopy
Elyor il

1
-Dal momento che gli indici vengono utilizzati anche per il layout fisico dei record db. Non sono sicuro se la rimozione di indici in qualsiasi database sia una buona idea.
Farjad,

Ma il tuo consigliato, niente in memoria !!! E se la dimensione del tuo batch può essere un numero piccolo, molto, molto male funzionato è la sua classe :( Provo la classe CopyIn npgsql, perché è come un mapping formattato CSV nelle istruzioni della query PG. Puoi provare per Big Table?
Elyor

94

Esiste un'alternativa all'utilizzo di COPY, che è la sintassi dei valori multirow che supporta Postgres. Dalla documentazione :

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

Il codice sopra inserisce due righe, ma è possibile estenderlo arbitrariamente, fino a quando non si raggiunge il numero massimo di token di istruzione preparati (potrebbe essere $ 999, ma non ne sono sicuro al 100%). A volte non si può usare COPIA, e questo è un degno sostituto per quelle situazioni.


12
Sai come le prestazioni di questo metodo sono paragonabili a COPIA?
Concedi Humphries il

Se riscontri un problema con le autorizzazioni, prima di provare questo, usa COPIA ... DA STDIN
Andrew Scott Evans,

Se stai utilizzando la sicurezza a livello di riga, questo è il meglio che puoi fare. "COPIA DA non è supportato per le tabelle con sicurezza a livello di riga" dalla versione 12.
Eloff

COPY è molto più veloce di INSERT esteso
hipertracker

24

Un modo per velocizzare le cose è eseguire esplicitamente più inserti o copie all'interno di una transazione (diciamo 1000). Il comportamento predefinito di Postgres è il commit dopo ogni istruzione, quindi raggruppando i commit, è possibile evitare un certo sovraccarico. Come dice la guida nella risposta di Daniel, potrebbe essere necessario disabilitare l'autocommit perché funzioni. Nota anche il commento in basso che suggerisce di aumentare le dimensioni dei wal_buffer a 16 MB.


1
Vale la pena ricordare che il limite per il numero di inserti / copie che è possibile aggiungere alla stessa transazione è probabilmente molto più alto di qualsiasi cosa tu provi. È possibile aggiungere milioni e milioni di righe all'interno della stessa transazione e non incorrere in problemi.
Sumeet Jain,

@SumeetJain Sì, sto solo osservando il "punto ottimale" della velocità in termini di numero di copie / inserti per transazione.
Dana the Sane,

Questo bloccherà la tabella mentre la transazione è in esecuzione?
Lambda Fairy,

15

UNNESTla funzione con le matrici può essere utilizzata insieme alla sintassi multirow VALUES. Penso che questo metodo sia più lento dell'uso COPYma è utile per me nel lavoro con psycopg e python (python listpassato a cursor.executediventa pg ARRAY):

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

senza VALUESutilizzare la sottoselezione con ulteriore controllo dell'esistenza:

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

la stessa sintassi per gli aggiornamenti collettivi:

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

Dipende principalmente dall'attività (altra) nel database. Operazioni come questa congelano efficacemente l'intero database per altre sessioni. Un'altra considerazione è il modello di dati e la presenza di vincoli, trigger, ecc.

Il mio primo approccio è sempre: creare una tabella (temp) con una struttura simile alla tabella di destinazione (creare una tabella tmp AS selezionare * dalla destinazione dove 1 = 0) e iniziare leggendo il file nella tabella temporanea. Quindi controllo cosa è possibile verificare: duplicati, chiavi già esistenti nella destinazione, ecc.

Quindi faccio semplicemente un "do insert in target select * from tmp" o simile.

Se fallisce o impiega troppo tempo, lo interrompo e considero altri metodi (eliminazione temporanea di indici / vincoli, ecc.)



6

Ho appena riscontrato questo problema e consiglierei csvsql ( versioni ) per le importazioni in blocco in Postgres. Per eseguire un inserimento in blocco, devi semplicemente createdbe poi utilizzare csvsql, che si collega al tuo database e crea tabelle individuali per un'intera cartella di CSV.

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

1
Per csvsql, al fine di pulire anche il csv di origine da eventuali errori di formattazione, è meglio seguire queste istruzioni , più documentazione qui
sal

0

Il file esterno è il migliore e tipico bulk data

Il termine "dati in blocco" si riferisce a "molti dati", quindi è naturale utilizzare dati grezzi originali , senza la necessità di trasformarli in SQL. I file di dati grezzi tipici per "l'inserimento di massa" sono CSV e JSON formati .

Inserimento in blocco con qualche trasformazione

Nelle applicazioni ETL e nei processi di acquisizione, è necessario modificare i dati prima di inserirli. La tabella temporanea consuma (molto) spazio su disco e non è il modo più veloce per farlo. Il wrapper di dati esterni (FDW) PostgreSQL è la scelta migliore.

Esempio CSV . Supponiamo che tablename (x, y, z)su SQL e un file CSV come

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

È possibile utilizzare il classico SQL COPYper caricare ( come i dati originali) tmp_tablename, inserire i dati filtrati in tablename... Ma, per evitare il consumo del disco, il meglio è ingerire direttamente da

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

Devi preparare il database per FDW e invece statico tmp_tablename_fdwpuoi usare una funzione che lo genera :

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

Esempio JSON . Un set di due file myRawData1.jsone Ranger_Policies2.jsonpuò essere ingerito da:

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
;

dove la funzione jsonb_read_files () legge tutti i file di una cartella, definita da una maschera:

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;

Mancanza di streaming gzip

Il metodo più frequente di "ingestione di file" (principalmente in Big Data) è preservare il file originale in formato gzip e trasferirlo con algoritmo di streaming , tutto ciò che può essere eseguito velocemente e senza consumo di disco in pipe unix:

 gunzip remote_or_local_file.csv.gz | convert_to_sql | psql 

Quindi l'ideale (futuro) è un'opzione server per il formato .csv.gz.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.