2010-10-15 11 views
12

Ich entwickle ein Programm in Python, das mit MySQLdb auf eine MySQL-Datenbank zugreift. In bestimmten Situationen muss ich einen INSERT- oder REPLACE-Befehl in vielen Zeilen ausführen. Ich mache es gerade so:Warum ist Executemany in Python MySQLdb langsam?

db.execute("REPLACE INTO " + table + " (" + ",".join(cols) + ") VALUES" + 
    ",".join(["(" + ",".join(["%s"] * len(cols)) + ")"] * len(data)), 
    [row[col] for row in data for col in cols]) 

Es funktioniert gut, aber es ist irgendwie peinlich. Ich habe mich gefragt, ob ich es einfacher machen könnte, zu lesen, und ich habe von dem Befehl executemany erfahren. Ich änderte meinen Code, um wie folgt auszusehen:

db.executemany("REPLACE INTO " + table + " (" + ",".join(cols) + ") " + 
    "VALUES(" + ",".join(["%s"] * len(cols)) + ")", 
    [tuple(row[col] for col in cols) for row in data]) 

Es funktionierte immer noch, aber es lief viel langsamer. In meinen Tests lief es für relativ kleine Datensätze (etwa 100-200 Zeilen) etwa 6-mal langsamer. Für große Datenmengen (etwa 13.000 Zeilen, die größte, die ich erwarten würde), lief es etwa 50 mal langsamer. Warum macht es das?

Ich möchte meinen Code wirklich vereinfachen, aber ich möchte nicht den großen Rückgang in der Leistung. Kennt jemand irgendeinen Weg, um es schneller zu machen?

Ich benutze Python 2.7 und MySQLdb 1.2.3. Ich habe versucht, an der setupputsize-Funktion herumzubasteln, aber das schien nichts zu tun. Ich habe den MySQLdb-Quellcode angeschaut und es sieht so aus, als ob er nichts tun sollte.

+0

Wie viele Zeilen werden eingefügt/ersetzt? Ihre zweite Anweisung erstellt eine riesige Liste im Speicher, bevor sie an mysql übergeben wird. – nosklo

+1

Ich ersetze bis zu 13.000 Zeilen. Ich denke nicht, dass das Erstellen der Liste der Flaschenhals ist. Wenn ich die Liste erstelle, sie aber nicht an den db-Cursor übergebe, braucht es kaum Zeit. –

+0

(Ich werde die Frage nicht beantworten, aber ...) 'INSERT ... ON DUPLICATE KEY UPDATE ...' ist fast immer besser als 'REPLACE ...'. –

Antwort

19

Versuchen Sie, das Wort 'Werte' in Ihrer Abfrage zu senken - dies scheint ein Bug/Regression in MySQL-Python 1.2.3 zu sein.

Die Implementierung von executemany() in MySQL-python vergleicht die VALUES-Klausel mit einem regulären Ausdruck und klont dann nur die Werteliste für jede Datenzeile, sodass Sie genau die gleiche Abfrage ausführen wie bei Ihrer ersten Methode.

Leider hat der reguläre Ausdruck in dieser Version das Flag für die Groß- und Kleinschreibung verloren (anschließend in Trunk r622 fixiert, aber nie wieder in den Zweig 1.2 rückversetzt), so dass er die Daten iteriert und eine Abfrage pro Zeile absetzt.

+0

Ich habe das versucht und es funktioniert! Mit "Werten" in Kleinbuchstaben ist es bei Executemany genauso schnell wie bei Execute oder manchmal etwas schneller. –

+1

Beachten Sie, dass die 1.2.3-Regex nicht mit Argumenten in ON DUPLICATE KEY UPDATE-Abfragen funktioniert (die Regex stimmt nur mit den ersten Argumenten überein), also können Werte mit Unterüberlagerung zu Verwirrung führen (weil sie mit execute() arbeiten) Alle Argumente wurden während der Formatierung von Zeichenfolgen konvertiert. Um dies zu vermeiden, verwenden Sie das Format VALUES() anstelle von Argumenten im Abschnitt ON DUPLICATE KEY der Abfrage. –

+0

Es wurde in [1.2.4] (https://github.com/farcepest/MySQLdb1/blob/MySQLdb-1.2.4/MySQLdb/cursors.py#L43) behoben. – saaj

1

Ihr erstes Beispiel ist eine einzelne (große) Anweisung, die generiert und dann an die Datenbank gesendet wird.

Das zweite Beispiel ist eine viel einfachere Anweisung, die eine einzelne Zeile einfügt/ersetzt, aber mehrfach ausgeführt wird. Jeder Befehl wird separat an die Datenbank gesendet, sodass Sie die Bearbeitungszeit von Client zu Server und zurück für jede eingefügte Zeile bezahlen müssen. Ich würde denken, dass diese zusätzliche Latenz zwischen den Befehlen der Hauptgrund für die verringerte Leistung des zweiten Beispiels ist.

+0

Das habe ich vermutet.Ich dachte, vielleicht ist die Executemany-Funktion raffiniert genug, um die Befehle in einer Abfrage zu senden, aber es scheint nicht so. –

1

Dringend nicht empfehlen, executeMany in pyodbc sowie ceodbc beide langsam und enthält eine Menge Fehler zu verwenden.

Verwenden Sie stattdessen execute und erstellen Sie manuell SQL Abfrage mit einfachen String-Format.

transaction = "TRANSACTION BEGIN {0} COMMIT TRANSACTION 

bulkRequest = "" 
for i in range(0, 100) 
    bulkRequest = bulkRequest + "INSERT INTO ...... {0} {1} {2}" 

ceodbc.execute(transaction.format(bulkRequest)) 

Aktuelle Implementierung ist sehr einfach schnell und zuverlässig.