Quel est le moyen le plus rapide de faire un insert en vrac dans Postgres?

242

J'ai besoin d'insérer par programme des dizaines de millions d'enregistrements dans une base de données PostgreSQL. Actuellement, j'exécute des milliers d'instructions d'insertion dans une seule "requête".

Y a-t-il une meilleure façon de le faire, une instruction d'insertion en masse que je ne connais pas?

Cendre
la source

Réponses:

211

PostgreSQL a un guide sur la meilleure façon de remplir une base de données au départ, et ils suggèrent d'utiliser la commande COPY pour le chargement en masse des lignes. Le guide contient d'autres bons conseils sur la façon d'accélérer le processus, comme la suppression des index et des clés étrangères avant de charger les données (et de les rajouter par la suite).

Dan Lew
la source
33
J'ai également écrit un peu plus de détails pour développer dans stackoverflow.com/questions/12206600/… .
Craig Ringer
24
@CraigRinger Wow, "un peu plus de détails" est le meilleur euphémisme que j'ai vu de toute la semaine;)
culix
Essayez le package d'installation NpgsqlBulkCopy
Elyor
1
-Puisque les index sont également utilisés pour la présentation physique des enregistrements db. Je ne sais pas si la suppression d'index dans une base de données est une bonne idée.
Farjad
Mais votre recommandé, rien en mémoire !!! Et si la taille de votre lot peut être petite, très-très mal a fonctionné sa classe :( J'essaie la classe CopyIn de npgsql, car c'est comme un mappage au format CSV dans les instructions de requête PG. Vous pouvez essayer pour Big Table?
Elyor
94

Il existe une alternative à l'utilisation de COPY, qui est la syntaxe des valeurs à plusieurs lignes prise en charge par Postgres. De la documentation :

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

Le code ci-dessus insère deux lignes, mais vous pouvez l'étendre arbitrairement, jusqu'à ce que vous atteigniez le nombre maximal de jetons d'instruction préparés (il peut s'agir de 999 $, mais je n'en suis pas sûr à 100%). Parfois, on ne peut pas utiliser COPY, et c'est un remplacement valable pour ces situations.

Ben Harper
la source
12
Savez-vous comment les performances de cette méthode se comparent à COPY?
Grant Humphries
Si vous rencontrez un problème d'autorisations, avant d'essayer, utilisez COPY ... FROM STDIN
Andrew Scott Evans
Si vous utilisez la sécurité au niveau des lignes, c'est le mieux que vous puissiez faire. "COPY FROM n'est pas pris en charge pour les tables avec une sécurité au niveau des lignes" à partir de la version 12.
Eloff
COPY est beaucoup plus rapide que INSERT étendu
hipertracker
24

Une façon d'accélérer les choses consiste à effectuer explicitement plusieurs insertions ou copies dans une transaction (disons 1000). Le comportement par défaut de Postgres est de valider après chaque instruction, donc en regroupant les validations, vous pouvez éviter certains frais généraux. Comme le dit le guide dans la réponse de Daniel, vous devrez peut-être désactiver la validation automatique pour que cela fonctionne. Notez également le commentaire en bas qui suggère d'augmenter la taille des wal_buffers à 16 Mo peut également aider.

Dana la saine
la source
1
Il convient de mentionner que la limite du nombre d'insertions / copies que vous pouvez ajouter à la même transaction est probablement beaucoup plus élevée que tout ce que vous tenterez. Vous pouvez ajouter des millions et des millions de lignes dans la même transaction et ne pas rencontrer de problèmes.
Sumeet Jain
@SumeetJain Oui, je fais juste une remarque sur le «point idéal» de vitesse en termes de nombre de copies / encarts par transaction.
Dana la saine
Cela verrouillera-t-il la table pendant l'exécution de la transaction?
Lambda Fairy
15

UNNESTLa fonction avec des tableaux peut être utilisée avec la syntaxe VALUES à plusieurs lignes. Je pense que cette méthode est plus lente que son utilisation, COPYmais elle m'est utile dans le travail avec psycopg et python (python listpassé à cursor.executedevient pg ARRAY):

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

sans VALUESutiliser subselect avec contrôle d'existence supplémentaire:

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 même syntaxe pour les mises à jour groupées:

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
la source
9

Cela dépend principalement de l'activité (autre) dans la base de données. De telles opérations gèlent efficacement la base de données entière pour d'autres sessions. Une autre considération est le modèle de données et la présence de contraintes, de déclencheurs, etc.

Ma première approche est toujours: créer une table (temporaire) avec une structure similaire à la table cible (créer une table tmp AS select * à partir de la cible où 1 = 0), et commencer par lire le fichier dans la table temporaire. Ensuite, je vérifie ce qui peut être vérifié: les doublons, les clés qui existent déjà dans la cible, etc.

Ensuite, je fais juste un "ne insérer dans la cible select * from tmp" ou similaire.

Si cela échoue ou prend trop de temps, je l'abandonne et j'envisage d'autres méthodes (suppression temporaire des index / contraintes, etc.)

sauvage
la source
6

Je viens de rencontrer ce problème et je recommanderais csvsql ( versions ) pour les importations en vrac vers Postgres. Pour effectuer une insertion en bloc, vous devez simplement createdbet ensuite utiliser csvsql, qui se connecte à votre base de données et crée des tables individuelles pour un dossier entier de CSV.

$ createdb test 
$ csvsql --db postgresql:///test --insert examples/*.csv
Sarah Frostenson
la source
1
Pour csvsql, afin de nettoyer également le csv source de toute erreur de formatage possible, il est préférable de suivre ces instructions , plus de documentation ici
sal
0

Le fichier externe est le meilleur et typique des données en masse

Le terme «données en masse» est lié à «beaucoup de données», il est donc naturel d'utiliser des données brutes originales , sans avoir besoin de les transformer en SQL. Les fichiers de données brutes typiques pour "insertion en bloc" sont CSV et JSON formats .

Insert en vrac avec une certaine transformation

Dans les applications ETL et les processus d'ingestion, nous devons modifier les données avant de les insérer. La table temporaire consomme (beaucoup) d'espace disque et ce n'est pas le moyen le plus rapide de le faire. Le wrapper de données étrangères (FDW) PostgreSQL est le meilleur choix.

Exemple CSV . Supposons que tablename (x, y, z)le SQL et un fichier CSV comme

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

Vous pouvez utiliser le SQL classique COPYpour charger ( comme les données d'origine) dans tmp_tablename, les insérer des données filtrées dans tablename... Mais, pour éviter la consommation de disque, le mieux est d'ingérer directement par

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

Vous devez préparer la base de données pour FDW, et à la place statique, tmp_tablename_fdwvous pouvez utiliser une fonction qui la génère :

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

Exemple JSON . Un ensemble de deux fichiers, myRawData1.jsonet Ranger_Policies2.jsonpeut être ingéré par:

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
;

où la fonction jsonb_read_files () lit tous les fichiers d'un dossier, définis par un masque:

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;

Manque de streaming gzip

La méthode la plus fréquente pour «l'ingestion de fichiers» (principalement dans le Big Data) est de conserver le fichier d'origine au format gzip et de le transférer avec un algorithme de streaming , tout ce qui peut fonctionner rapidement et sans consommation de disque dans les canaux Unix:

 gunzip remote_or_local_file.csv.gz | convert_to_sql | psql 

Donc, idéal (futur) est une option de serveur pour le formatage .csv.gz.

Peter Krauss
la source