Was ist der schnellste Weg, um eine Masseneinfügung in Postgres durchzuführen?

241

Ich muss programmgesteuert 10 Millionen Datensätze in eine Postgres-Datenbank einfügen. Gegenwärtig führe ich Tausende von Einfügeanweisungen in einer einzigen "Abfrage" aus.

Gibt es einen besseren Weg, dies zu tun, eine Bulk-Insert-Anweisung, von der ich nichts weiß?

Asche
quelle

Antworten:

211

PostgreSQL enthält eine Anleitung, wie eine Datenbank zunächst am besten gefüllt werden kann, und es wird empfohlen, den Befehl COPY zum Laden von Zeilen in großen Mengen zu verwenden . Das Handbuch enthält einige weitere gute Tipps zur Beschleunigung des Vorgangs, z. B. das Entfernen von Indizes und Fremdschlüsseln vor dem Laden der Daten (und das anschließende Hinzufügen dieser Daten).

Dan Lew
quelle
33
Ich habe ein bisschen mehr Details geschrieben, um es auch in stackoverflow.com/questions/12206600/… zu erläutern .
Craig Ringer
24
@CraigRinger Wow, "ein bisschen mehr Detail" ist die beste Untertreibung, die ich die ganze Woche gesehen habe;)
culix
Versuchen Sie Install-Package NpgsqlBulkCopy
Elyor
1
- Da Indizes auch für das physische Layout der Datenbankdatensätze verwendet werden. Ich bin mir nicht sicher, ob das Entfernen von Indizes in einer Datenbank eine gute Idee ist.
Farjad
Aber dein empfohlen, nichts im Gedächtnis !!! Und wenn Ihre Stapelgröße eine kleine Zahl sein kann, hat die Klasse sehr, sehr schlecht funktioniert :( Ich versuche die npgsql CopyIn-Klasse, weil sie wie eine CSV-formatierte Zuordnung in PG-Abfrageanweisungen ist. Sie können es mit Big Table versuchen?
Elyor
93

Es gibt eine Alternative zur Verwendung von COPY, der von Postgres unterstützten Syntax für Mehrzeilenwerte. Aus der Dokumentation :

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

Der obige Code fügt zwei Zeilen ein, aber Sie können ihn beliebig erweitern, bis Sie die maximale Anzahl vorbereiteter Anweisungstoken erreicht haben (es könnte 999 US-Dollar sein, aber da bin ich mir nicht 100% sicher). Manchmal kann man COPY nicht verwenden, und dies ist ein würdiger Ersatz für diese Situationen.

Ben Harper
quelle
12
Wissen Sie, wie die Leistung dieser Methode im Vergleich zu COPY ist?
Grant Humphries
Wenn Sie auf ein Berechtigungsproblem stoßen, bevor Sie dies versuchen, verwenden Sie COPY ... FROM STDIN
Andrew Scott Evans
Wenn Sie Sicherheit auf Zeilenebene verwenden, ist dies das Beste, was Sie tun können. "COPY FROM wird für Tabellen mit Sicherheit auf
Zeilenebene
COPY ist viel schneller als Extended INSERT
Hipertracker
24

Eine Möglichkeit, die Dinge zu beschleunigen, besteht darin, explizit mehrere Einfügungen oder Kopien innerhalb einer Transaktion durchzuführen (z. B. 1000). Das Standardverhalten von Postgres besteht darin, nach jeder Anweisung ein Commit durchzuführen. Wenn Sie also die Commits stapeln, können Sie einen gewissen Overhead vermeiden. Wie der Leitfaden in Daniels Antwort sagt, müssen Sie möglicherweise die automatische Festschreibung deaktivieren, damit dies funktioniert. Beachten Sie auch den Kommentar unten, der darauf hinweist, dass die Größe der wal_buffers auf 16 MB erhöht werden kann.

Dana die Gesunde
quelle
1
Es ist erwähnenswert, dass das Limit für die Anzahl der Einfügungen / Kopien, die Sie derselben Transaktion hinzufügen können, wahrscheinlich viel höher ist als alles, was Sie versuchen werden. Sie können innerhalb derselben Transaktion Millionen und Abermillionen von Zeilen hinzufügen, ohne auf Probleme zu stoßen.
Sumeet Jain
@SumeetJain Ja, ich möchte nur auf die Geschwindigkeit 'Sweet Spot' in Bezug auf die Anzahl der Kopien / Beilagen pro Transaktion hinweisen.
Dana the Sane
Wird dies die Tabelle sperren, während die Transaktion ausgeführt wird?
Lambda Fairy
15

UNNESTFunktion mit Arrays kann zusammen mit der Multirow-VALUES-Syntax verwendet werden. Ich denke , ich , dass diese Methode ist langsamer als die Verwendung , COPYaber es ist nützlich für mich in der Arbeit mit psycopg und Python (Python listweitergegeben cursor.executewird pg ARRAY):

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

ohne VALUESUnterauswahl mit zusätzlicher Existenzprüfung:

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

die gleiche Syntax für Massenaktualisierungen:

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

Dies hängt hauptsächlich von der (anderen) Aktivität in der Datenbank ab. Mit solchen Vorgängen wird die gesamte Datenbank für andere Sitzungen effektiv eingefroren. Eine weitere Überlegung ist das Datenmodell und das Vorhandensein von Einschränkungen, Auslösern usw.

Mein erster Ansatz ist immer: Erstellen Sie eine (temporäre) Tabelle mit einer Struktur ähnlich der Zieltabelle (erstellen Sie eine Tabelle tmp AS select * from target mit 1 = 0) und lesen Sie zunächst die Datei in die temporäre Tabelle. Dann überprüfe ich, was überprüft werden kann: Duplikate, Schlüssel, die bereits im Ziel vorhanden sind usw.

Dann mache ich einfach ein "In Ziel einfügen select * from tmp" oder ähnliches.

Wenn dies fehlschlägt oder zu lange dauert, brich ich es ab und erwäge andere Methoden (vorübergehendes Löschen von Indizes / Einschränkungen usw.)

Wildplasser
quelle
6

Ich bin gerade auf dieses Problem gestoßen und würde csvsql ( Releases ) für Massenimporte nach Postgres empfehlen . Um eine Masseneinfügung durchzuführen, verwenden Sie einfach createdbund dann eine csvsql, die eine Verbindung zu Ihrer Datenbank herstellt und einzelne Tabellen für einen gesamten Ordner mit CSVs erstellt.

$ createdb test 
$ csvsql --db postgresql:///test --insert examples/*.csv
Sarah Frostenson
quelle
1
Für csvsql ist es am besten, diese Anweisungen zu befolgen, um die Quell-csv auch von möglichen Formatierungsfehlern zu befreien. Weitere Dokumentation finden Sie hier
Sal
0

Die externe Datei ist die besten und typischsten Massendaten

Der Begriff "Massendaten" bezieht sich auf "viele Daten". Daher ist es selbstverständlich, Original-Rohdaten zu verwenden , ohne diese in SQL umwandeln zu müssen. Typische Rohdatendateien für "Masseneinfügungen" sind CSV- und JSON- Formate.

Masseneinsatz mit etwas Transformation

In ETL- Anwendungen und Aufnahmeprozessen müssen wir die Daten vor dem Einfügen ändern. Temporäre Tabellen belegen (viel) Speicherplatz, und dies ist nicht der schnellere Weg. Der PostgreSQL Foreign Wrapper (FDW) ist die beste Wahl.

CSV-Beispiel . Angenommen, die tablename (x, y, z)On-SQL- und eine CSV-Datei mögen

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

Sie können das klassische SQL COPYzum Laden ( wie auch für Originaldaten) verwenden tmp_tablenameund gefilterte Daten in tablename... einfügen. Um jedoch den Festplattenverbrauch zu vermeiden, sollten Sie am besten direkt von aufnehmen

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

Sie müssen die Datenbank für FDW vorbereiten, und stattdessen tmp_tablename_fdwkönnen Sie statisch eine Funktion verwenden, die sie generiert :

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-Beispiel . Ein Satz von zwei Dateien, myRawData1.jsonund Ranger_Policies2.jsonkann durch eingenommen werden:

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
;

Dabei liest die Funktion jsonb_read_files () alle Dateien eines Ordners, die durch eine Maske definiert sind:

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;

Fehlendes gzip-Streaming

Die häufigste Methode zur "Dateiaufnahme" (hauptsächlich in Big Data) besteht darin, die Originaldatei im gzip-Format beizubehalten und mit zu übertragen Streaming-Algorithmus zu alles, was in Unix-Pipes schnell und ohne Disc-Verbrauch ausgeführt werden kann:

 gunzip remote_or_local_file.csv.gz | convert_to_sql | psql 

Ideal (Zukunft) ist also eine Serveroption für das Format .csv.gz.

Peter Krauss
quelle