Qual é a maneira mais rápida de fazer uma inserção em massa no Postgres?

241

Preciso inserir programaticamente 10 milhões de registros em um banco de dados do postgres. Atualmente estou executando milhares de instruções de inserção em uma única "consulta".

Existe uma maneira melhor de fazer isso, alguma instrução de inserção em massa que eu não conheço?

Cinza
fonte

Respostas:

211

O PostgreSQL tem um guia sobre como preencher melhor um banco de dados inicialmente e sugere o uso do comando COPY para linhas de carregamento em massa. O guia tem outras boas dicas sobre como acelerar o processo, como remover índices e chaves estrangeiras antes de carregar os dados (e adicioná-los novamente depois).

Dan Lew
fonte
33
Escrevi um pouco mais detalhadamente para elaborar também em stackoverflow.com/questions/12206600/… .
Craig Ringer
24
@CraigRinger Uau, "um pouco mais detalhadamente" é o melhor eufemismo Tenho visto toda a semana;)
culix
Tente instalar o pacote NpgsqlBulkCopy
Elyor
1
-Desde que os índices também são usados ​​para o layout físico dos registros db. Não tenho certeza se a remoção de índices em qualquer banco de dados é uma boa idéia.
Farjad
Mas o seu recomendado, nada na memória !!! E se o seu tamanho do lote pode ser o número pequeno, muito-muito ruim trabalhou sua classe :( I Try Npgsql classe copyin, porque é como como CSV formatado mapeamento em PG instrução de consulta de Você pode tentar para Big Table.?
Elyor
93

Existe uma alternativa ao uso de COPY, que é a sintaxe dos valores de múltiplas linhas suportada pelo Postgres. A partir da documentação :

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

O código acima insere duas linhas, mas você pode estendê-lo arbitrariamente, até atingir o número máximo de tokens de instrução preparados (pode ser US $ 999, mas não tenho 100% de certeza disso). Às vezes, não se pode usar COPY, e esse é um substituto digno para essas situações.

Ben Harper
fonte
12
Você sabe como o desempenho deste método se compara ao COPY?
Grant Humphries
Se você tiver um problema de permissões, antes de tentar isso, o uso COPY ... FROM STDIN
Andrew Scott Evans
Se você estiver usando segurança no nível de linha, é o melhor que pode fazer. "COPY FROM não é suportado para tabelas com segurança em nível de linha" a partir da versão 12.
Eloff
COPY é muito mais rápido que o INSERT estendido
hipertracker 01/02
24

Uma maneira de acelerar as coisas é executar explicitamente várias inserções ou cópias em uma transação (por exemplo, 1000). O comportamento padrão do Postgres é confirmar após cada instrução, portanto, enviando em lotes as confirmações, você pode evitar alguma sobrecarga. Como o guia da resposta de Daniel diz, talvez seja necessário desativar o autocommit para que isso funcione. Observe também o comentário na parte inferior, que sugere aumentar o tamanho dos wal_buffers para 16 MB, também pode ajudar.

Dana the Sane
fonte
1
Vale ressaltar que o limite de quantas inserções / cópias você pode adicionar à mesma transação é provavelmente muito maior do que qualquer coisa que você tente. Você pode adicionar milhões e milhões de linhas na mesma transação e não ter problemas.
Sumeet Jain
@SumeetJain Sim, estou apenas comentando a velocidade do ponto ideal em termos de número de cópias / inserções por transação.
Dana the Sane
Isso bloqueará a tabela enquanto a transação estiver em execução?
Lambda Fairy
15

UNNESTA função com matrizes pode ser usada junto com a sintaxe VALUES de múltiplas linhas. Eu acho que esse método é mais lento que o uso, COPYmas é útil para mim no trabalho com psycopg e python (python listpassado para cursor.executetorna - se pg ARRAY):

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

sem VALUESusar a subseleção com verificação de existência adicional:

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

a mesma sintaxe para atualizações em massa:

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
fonte
9

Depende principalmente da (outra) atividade no banco de dados. Operações como essa congelam efetivamente todo o banco de dados para outras sessões. Outra consideração é o modelo de dados e a presença de restrições, gatilhos, etc.

Minha primeira abordagem é sempre: criar uma tabela (temp) com uma estrutura semelhante à tabela de destino (criar tabela tmp AS select * from target onde 1 = 0) e começar lendo o arquivo na tabela temp. Depois, verifico o que pode ser verificado: duplicatas, chaves que já existem no destino, etc.

Então eu apenas faço um "insira no destino, selecione * de tmp" ou similar.

Se isso falhar ou demorar muito, eu a aboro e considero outros métodos (eliminação temporária de índices / restrições, etc.)

wildplasser
fonte
6

Acabei de encontrar esse problema e recomendaria o csvsql ( releases ) para importações em massa do Postgres. Para executar uma inserção em massa, você simplesmente createdbusaria csvsqle conectaria ao seu banco de dados e criaria tabelas individuais para uma pasta inteira de CSVs.

$ createdb test 
$ csvsql --db postgresql:///test --insert examples/*.csv
Sarah Frostenson
fonte
1
Para o csvsql, para limpar também o csv de origem de possíveis erros de formatação, é melhor seguir estas instruções , mais documentação aqui
sal
0

O arquivo externo é o melhor e típico volume de dados

O termo "dados em massa" está relacionado a "muitos dados"; portanto, é natural usar dados brutos originais , sem a necessidade de transformá-los em SQL. Arquivos de dados brutos típicos para "inserção em massa" são CSV e JSON formatos .

Inserção em massa com alguma transformação

Nos aplicativos ETL e nos processos de processamento, precisamos alterar os dados antes de inseri-los. A tabela temporária consome (muito) espaço em disco e não é a maneira mais rápida de fazê-lo. O wrapper de dados estrangeiros do PostgreSQL (FDW) é a melhor opção.

Exemplo de CSV . Suponha que tablename (x, y, z)no SQL e um arquivo CSV como

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

Você pode usar o SQL clássico COPYpara carregar ( como os dados originais) tmp_tablename, inserir dados filtrados em tablename... Mas, para evitar o consumo de disco, o melhor é ingerir diretamente por

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

Você precisa preparar o banco de dados para o FDW e, em vez estático, tmp_tablename_fdwpode usar uma função que o gera :

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

Exemplo JSON . Um conjunto de dois arquivos myRawData1.jsone Ranger_Policies2.jsonpode ser ingerido por:

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
;

onde a função jsonb_read_files () lê todos os arquivos de uma pasta, definida por uma máscara:

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;

Falta de streaming gzip

O método mais frequente para "ingestão de arquivos" (principalmente no Big Data) é preservar o arquivo original no formato gzip e transferi-lo com o algoritmo de streaming , qualquer coisa que possa ser executada rapidamente e sem consumo de disco em pipes unix:

 gunzip remote_or_local_file.csv.gz | convert_to_sql | psql 

Tão ideal (futuro) é uma opção de servidor para formato .csv.gz.

Peter Krauss
fonte