2008-12-28 12 views
6

Ich analysiere gerade eine Wikipedia-Dump-Datei; Ich extrahiere eine Reihe von Daten daraus mit Python und behalte es in einer PostgreSQL-Datenbank. Ich versuche immer, die Dinge schneller zu machen, weil diese Datei riesig ist (18 GB). Um mit PostgreSQL zu interagieren, verwende ich psycopg2, aber dieses Modul scheint viele andere solche DBAPIs nachzuahmen.Python-PostgreSQL psycopg2 Schnittstelle -> Executemany

Wie auch immer, ich habe eine Frage bezüglich cursor.executemany (Befehl, Werte); es scheint mir, als würde man eine Executemany einmal alle 1000 Werte ausführen oder so ist es besser als cursor.execute (command% value) für jeden dieser 5 Millionen Werte aufzurufen (bitte bestätigen oder korrigieren Sie mich!).

Aber Sie sehen, ich verwende eine Executemany, um 1000 Zeilen in eine Tabelle einzufügen, die eine UNIQUE Integritätsbedingung hat; Diese Einschränkung wird vorher nicht in Python verifiziert, da ich entweder ständig SELECT (das scheint kontraproduktiv) oder mehr als 3 GB RAM benötige. All dies zu sagen, dass ich auf Postgres zu warnen, um mich zu warnen, als mein Skript versucht, eine bereits vorhandene Zeile durch Abfangen der psycopg2.DatabaseError einzufügen.

Wenn mein Skript eine solche nicht eindeutige INSERT erkennt, es connection.rollback() (was macht bis zu 1000 Zeilen jedes Mal, und macht die Executemany wertlos) und fügt dann alle Werte nacheinander ein.

Da psycopg2 so schlecht dokumentiert ist (wie so viele große Module ...), kann ich keine effiziente und effektive Problemumgehung finden. Ich habe die Anzahl der INSERT-Werte pro Executemany von 1000 auf 100 reduziert, um die Wahrscheinlichkeit eines nicht eindeutigen INSERT pro Executemany zu reduzieren, aber ich bin mir ziemlich sicher, dass sie psycopg2 einfach sagen können, diese Exceptions zu ignorieren oder das zu sagen Cursor, um die Ausführung fortzusetzen.

Im Grunde scheint dies die Art von Problem, die eine so einfache und beliebte Lösung hat, dass ich nur fragen kann, um darüber zu lernen.

Danke nochmal!

+0

Ich bin mir nicht sicher, aber ich denke, Executemany iteriert nur über Ihre Liste von Wörterbüchern (Zeilen) und ruft "Einfügen" auf jedem. Es macht also keinen Unterschied, ob Sie in einer Schleife ausführen oder Executemany aufrufen. Nur dass das "commit" nicht in loop aufgerufen werden sollte, sondern alle 100 oder 1000 mal. –

+0

so ist es: outerloop-> bekommt 1000 nächste Zeilen von der Liste -> gibt innere Schleife -> für jeden ausführen -> innere Schleife Exits -> commit -> Outerloop weiter bis Daten dauert. Sie können es auf einem 100.000 Datensatz gegen die Excutemany ausprobieren und prüfen, ob es einen Unterschied macht. –

+0

JV, also Sie sagen, dass eine ausführbare IPC kommuniziert mit PostgeSQL für jeden INSERT? Es ist der Overhead, der IPC innewohnt, den ich durch die Verwendung von Executemany zu beseitigen hoffe; wenn es das nicht beseitigt, habe ich nicht genug Grund, es zu benutzen. Danke, aber ich brauche noch mehr Überzeugung! -Nick –

Antwort

0

"Wenn mein Skript eine solche nicht eindeutige INSERT erkennt, es connection.rollback() (was macht bis zu 1000 Zeilen jedes Mal, und macht die Executemany wertlos) und fügt dann alle Werte eins nach dem anderen."

Die Frage macht nicht wirklich viel Sinn.

Scheitert JEDER Block von 1.000 Zeilen aufgrund von nicht eindeutigen Zeilen?

Scheitert 1 Block von 1.000 Zeilen (aus 5.000 solcher Blöcke)? Wenn das der Fall ist, dann hilft das Ausführen von vielen für 4.999 von 5.000 und ist bei weitem nicht "wertlos".

Sind Sie besorgt über diese nicht eindeutige Einlage? Oder haben Sie tatsächliche Statistiken darüber, wie oft das passiert?

Wenn Sie von 1.000 Zeilenblöcken zu 100 Zeilenblöcken gewechselt haben, können Sie - natürlich - feststellen, ob es einen Leistungsvorteil für 1.000 Zeilenblöcke, 100 Zeilenblöcke und 1 Zeilenblöcke gibt.

Bitte führen Sie tatsächlich das eigentliche Programm mit der tatsächlichen Datenbank und Blöcke unterschiedlicher Größe und veröffentlichen Sie die Zahlen.

8

kopieren Sie einfach alle Daten mit dem Befehl psql \ copy in eine Scratch-Tabelle oder verwenden Sie die Methode psycopg cursor.copy_in(). Dann:

Dies wird deduplizieren und läuft viel schneller als jede Kombination von Einsätzen.

-dg

-1

eine MERGE-Anweisung anstelle einer INSERT würde man Ihr Problem lösen.

+1

PostgreSQL unterstützt die MERGE-Anweisung ab Version 8.5 beta 2 überhaupt nicht. – intgr

4

Ich hatte das gleiche Problem und suchte hier viele Tage, um eine Menge Hinweise zu sammeln, um eine vollständige Lösung zu bilden. Auch wenn die Frage veraltet ist, hoffe ich, dass dies für andere nützlich sein wird.

1) Vergessen Sie Dinge über das Entfernen von Indizes/Constraints &, die sie später neu erstellen, Vorteile sind marginal oder schlechter.

2) Executemany ist besser als ausführen, wie es für Sie die Vorbereitung-Anweisung macht. Sie können die gleichen Ergebnisse sich mit einem Befehl wie den folgenden erhalten 300% Geschwindigkeit zu gewinnen:

# To run only once: 
sqlCmd = """PREPARE myInsert (int, timestamp, real, text) AS 
    INSERT INTO myBigTable (idNumber, date_obs, result, user) 
    SELECT $1, $2, $3, $4 WHERE NOT EXISTS 
    (SELECT 1 FROM myBigTable WHERE (idNumber, date_obs, user)=($1, $2, $4));""" 
curPG.execute(sqlCmd) 
cptInsert = 0 # To let you commit from time to time 

#... inside the big loop: 
curPG.execute("EXECUTE myInsert(%s,%s,%s,%s);", myNewRecord) 
allreadyExists = (curPG.rowcount < 1) 
if not allreadyExists: 
    cptInsert += 1 
    if cptInsert % 10000 == 0: 
     conPG.commit() 

Dieser Dummy-Tabelle Beispiel hat eine eindeutige Einschränkung auf (IDNumber, date_obs, Benutzer).

3) Die beste Lösung ist, COPY_FROM und einen TRIGGER zu verwenden, um den eindeutigen Schlüssel BEFORE INSERT zu verwalten. Das gab mir 36x mehr Geschwindigkeit. Ich begann mit normalen Inserts bei 500 Aufnahmen/Sek. und mit "copy" habe ich über 18.000 Datensätze/Sek. Beispielcode in Python mit Psycopg2:

ioResult = StringIO.StringIO() #To use a virtual file as a buffer 
cptInsert = 0 # To let you commit from time to time - Memory has limitations 
#... inside the big loop: 
    print >> ioResult, "\t".join(map(str, myNewRecord)) 
    cptInsert += 1 
    if cptInsert % 10000 == 0: 
     ioResult = flushCopyBuffer(ioResult, curPG) 
#... after the loop: 
ioResult = flushCopyBuffer(ioResult, curPG) 

def flushCopyBuffer(bufferFile, cursorObj): 
    bufferFile.seek(0) # Little detail where lures the deamon... 
    cursorObj.copy_from(bufferFile, 'myBigTable', 
     columns=('idNumber', 'date_obs', 'value', 'user')) 
    cursorObj.connection.commit() 
    bufferFile.close() 
    bufferFile = StringIO.StringIO() 
    return bufferFile 

Das ist es für den Python-Teil. Nun ist die Postgresql Trigger nicht Ausnahme psycopg2.IntegrityError haben und dann alle Datensätze der COPY-Befehl zurückgewiesen:

CREATE OR REPLACE FUNCTION chk_exists() 
    RETURNS trigger AS $BODY$ 
DECLARE 
    curRec RECORD; 
BEGIN 
    -- Check if record's key already exists or is empty (file's last line is) 
    IF NEW.idNumber IS NULL THEN 
     RETURN NULL; 
    END IF; 
    SELECT INTO curRec * FROM myBigTable 
     WHERE (idNumber, date_obs, user) = (NEW.idNumber, NEW.date_obs, NEW.user); 
    IF NOT FOUND THEN -- OK keep it 
     RETURN NEW; 
    ELSE  
     RETURN NULL; -- Oups throw it or update the current record 
    END IF; 
END; 
$BODY$ LANGUAGE plpgsql; 

Link Jetzt diese Funktion auf den Auslöser Ihrer Tabelle:

CREATE TRIGGER chk_exists_before_insert 
    BEFORE INSERT ON myBigTable FOR EACH ROW EXECUTE PROCEDURE chk_exists(); 

Dieses wie viel scheint der Arbeit, aber Postgresql ist ein sehr schnelles Biest, wenn es SQL nicht immer wieder interpretieren muss. Habe Spaß.