¿Cuál es la forma más rápida de hacer una inserción masiva en Postgres?

242

Necesito insertar programáticamente 10 de millones de registros en una base de datos postgres. Actualmente estoy ejecutando miles de declaraciones de inserción en una sola "consulta".

¿Hay una mejor manera de hacer esto, alguna declaración de inserción masiva que no conozca?

Ceniza
fuente

Respuestas:

211

PostgreSQL tiene una guía sobre cómo poblar mejor una base de datos inicialmente, y sugieren utilizar el comando COPIAR para filas de carga masiva. La guía tiene algunos otros buenos consejos sobre cómo acelerar el proceso, como eliminar índices y claves foráneas antes de cargar los datos (y agregarlos nuevamente).

Dan Lew
fuente
33
Escribí un poco más de detalles para elaborar en stackoverflow.com/questions/12206600/… también.
Craig Ringer
24
@CraigRinger Wow, "un poco más de detalle" es el mejor eufemismo que he visto en toda la semana;)
culix
Pruebe Install-Package NpgsqlBulkCopy
Elyor
1
-Desde que los índices también se utilizan para el diseño físico de los registros db. No estoy seguro si eliminar índices en alguna base de datos es una buena idea.
Farjad
Pero tu recomendado, ¡nada en la memoria! Y si el tamaño de su lote puede ser un número pequeño, muy, muy mal funcionó, es clase :( Intento con la clase npgsql CopyIn, porque es como un mapeo con formato CSV en la declaración de consulta PG. ¿Puede probar para Big Table?
Elyor
94

Existe una alternativa al uso de COPY, que es la sintaxis de valores de varias filas que admite Postgres. De la documentación :

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

El código anterior inserta dos filas, pero puede extenderlo arbitrariamente, hasta alcanzar el número máximo de tokens de declaración preparados (puede ser $ 999, pero no estoy 100% seguro de eso). A veces no se puede usar COPY, y este es un reemplazo digno para esas situaciones.

Ben Harper
fuente
12
¿Sabe cómo se compara el rendimiento de este método con COPIA?
Grant Humphries
Si se encuentra con un problema de permisos, antes de intentar esto, use COPIAR ... DE STDIN
Andrew Scott Evans
Si está utilizando seguridad de nivel de fila, esto es lo mejor que puede hacer. "COPY FROM no es compatible con tablas con seguridad de nivel de fila" a partir de la versión 12.
Eloff el
COPIA es mucho más rápido que insert
hipertracker
24

Una forma de acelerar las cosas es realizar explícitamente múltiples inserciones o copias dentro de una transacción (digamos 1000). El comportamiento predeterminado de Postgres es confirmar después de cada declaración, por lo que al agrupar las confirmaciones, puede evitar algunos gastos generales. Como dice la guía en la respuesta de Daniel, es posible que deba desactivar la confirmación automática para que esto funcione. También tenga en cuenta que el comentario en la parte inferior que sugiere aumentar el tamaño de wal_buffers a 16 MB también puede ayudar.

Dana la sana
fuente
1
Vale la pena mencionar que el límite para la cantidad de inserciones / copias que puede agregar a la misma transacción es probablemente mucho mayor que cualquier cosa que intente. Puede agregar millones y millones de filas dentro de la misma transacción y no tener problemas.
Sumeet Jain
@SumeetJain Sí, solo estoy comentando el "punto óptimo" de la velocidad en términos de la cantidad de copias / inserciones por transacción.
Dana la sana
¿Esto bloqueará la mesa mientras se ejecuta la transacción?
Lambda Fairy
15

UNNESTLa función con matrices se puede utilizar junto con la sintaxis VALORES de varias filas. Creo que este método es más lento que el uso, COPYpero es útil para mí en el trabajo con psycopg y python (python listpasado a se cursor.executeconvierte en pg ARRAY):

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

sin VALUESusar subseleccionar con verificación de existencia 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
);

la misma sintaxis para las actualizaciones masivas:

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

Depende principalmente de la (otra) actividad en la base de datos. Operaciones como esta congelan efectivamente toda la base de datos para otras sesiones. Otra consideración es el modelo de datos y la presencia de restricciones, disparadores, etc.

Mi primer enfoque es siempre: crear una tabla (temp) con una estructura similar a la tabla de destino (crear la tabla tmp AS select * from target donde 1 = 0), y comenzar leyendo el archivo en la tabla temporal. Luego verifico lo que se puede verificar: duplicados, claves que ya existen en el objetivo, etc.

Luego solo hago un "do insert en target select * from tmp" o similar.

Si esto falla o toma demasiado tiempo, lo aborto y considero otros métodos (descartar temporalmente índices / restricciones, etc.)

wildplasser
fuente
6

Acabo de encontrar este problema y recomendaría csvsql ( lanzamientos ) para importaciones masivas a Postgres. Para realizar una inserción masiva, simplemente createdbusaría y luego usaría csvsql, que se conecta a su base de datos y crea tablas individuales para una carpeta completa de CSV.

$ createdb test 
$ csvsql --db postgresql:///test --insert examples/*.csv
Sarah Frostenson
fuente
1
Para csvsql, para limpiar también el csv de origen de cualquier posible error de formateo, es mejor seguir estas instrucciones , más documentación aquí
sal
0

El archivo externo es el mejor y típico de datos masivos.

El término "datos masivos" está relacionado con "una gran cantidad de datos", por lo que es natural utilizar datos originales sin procesar , sin necesidad de transformarlos en SQL. Los archivos de datos en bruto típicos para "inserción masiva" son CSV y JSON formatos .

Inserción masiva con alguna transformación

En aplicaciones ETL y procesos de ingestión, necesitamos cambiar los datos antes de insertarlos. La tabla temporal consume (mucho) espacio en disco, y no es la forma más rápida de hacerlo. El contenedor de datos foráneos PostgreSQL (FDW) es la mejor opción.

Ejemplo de CSV . Supongamos que tablename (x, y, z)en SQL y un archivo CSV como

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

Puede usar el SQL clásico COPYpara cargar ( como son los datos originales) tmp_tablename, insertar datos filtrados en tablename... Pero, para evitar el consumo de disco, lo mejor es ingerir directamente por

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

Debe preparar la base de datos para FDW y, en su lugar, estática tmp_tablename_fdw, puede usar una función que la genere :

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 ejemplo . Un conjunto de dos archivos, myRawData1.jsony Ranger_Policies2.jsonpuede 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
;

donde la función jsonb_read_files () lee todos los archivos de una carpeta, definida por una 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 transmisión de gzip

El método más frecuente para la "ingestión de archivos" (principalmente en Big Data) es preservar el archivo original en formato gzip y transferirlo con un algoritmo de transmisión , cualquier cosa que pueda ejecutarse rápidamente y sin consumo de disco en tuberías Unix:

 gunzip remote_or_local_file.csv.gz | convert_to_sql | psql 

Así que ideal (futuro) es una opción de servidor para el formato .csv.gz.

Peter Krauss
fuente