Pandas: Verwenden Sie mehrzeilige Einfügungen für massive Beschleunigungen auf to_sql über Verbindungen mit hoher Latenz

Erstellt am 1. Dez. 2014  ·  48Kommentare  ·  Quelle: pandas-dev/pandas

Ich habe versucht, ~ 30.000 Zeilen mit pandas-0.15.1, oursql-0.9.3.1 und sqlalchemy-0.9.4 in eine MySQL-Datenbank einzufügen. Da sich die Maschine auf der anderen Seite des Atlantiks von mir befindet, dauerte das Aufrufen data.to_sql > 1 Stunde, um die Daten einzufügen. Bei der Inspektion mit Wireshark besteht das Problem darin, dass es für jede Zeile eine Einfügung sendet und dann auf die ACK wartet, bevor es die nächste sendet, und, um es kurz zu machen, die Ping-Zeiten bringen mich um.

Ich habe mich jedoch gemäß den Anweisungen von SQLAlchemy geändert

def _execute_insert(self, conn, keys, data_iter):
    data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
    conn.execute(self.insert_statement(), data)

zu

def _execute_insert(self, conn, keys, data_iter):
    data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
    conn.execute(self.insert_statement().values(data))

und der gesamte Vorgang ist in weniger als einer Minute abgeschlossen. (Um Ihnen einen Klick zu ersparen, besteht der Unterschied zwischen mehreren Anrufen bei insert into foo (columns) values (rowX) und einem massiven insert into foo (columns) VALUES (row1), (row2), row3) ). Angesichts der Tatsache, wie oft Menschen wahrscheinlich Pandas verwenden, um große Datenmengen einzufügen, fühlt sich dies wie ein großer Gewinn an, der großartig wäre, wenn er weiter verbreitet würde.

Einige Herausforderungen:

  • Nicht jede Datenbank unterstützt mehrzeilige Einfügungen (SQLite und SQLServer haben dies in der Vergangenheit nicht getan, tun dies jedoch jetzt). Ich weiß nicht, wie ich dies über SQLAlchemy überprüfen soll
  • Der von mir verwendete MySQL-Server erlaubte es mir nicht, die Daten auf einmal einzufügen, ich musste die Chunksize einstellen (5k funktionierte gut, aber ich denke, die vollen 30k waren zu viel). Wenn wir dies zur Standardeinfügung machen würden, müssten die meisten Leute eine Chunk-Größe hinzufügen (die möglicherweise schwer zu berechnen ist, da sie möglicherweise durch die maximale Paketgröße des Servers bestimmt wird).

Der einfachste Weg, dies zu tun, wäre, einen multirow= booleschen Parameter (Standardwert False ) zur Funktion to_sql hinzuzufügen und dann den Benutzer für die Einstellung der Chunksize verantwortlich zu machen, aber vielleicht gibt es einen besseren Weg?

Gedanken?

IO SQL Performance

Hilfreichster Kommentar

Wir haben herausgefunden, wie man einen Affenpatch macht – könnte für jemand anderen nützlich sein. Halten Sie diesen Code bereit, bevor Sie Pandas importieren.

from pandas.io.sql import SQLTable

def _execute_insert(self, conn, keys, data_iter):
    print "Using monkey-patched _execute_insert"
    data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
    conn.execute(self.insert_statement().values(data))

SQLTable._execute_insert = _execute_insert

Alle 48 Kommentare

Dies erscheint vernünftig. Danke, dass du das untersucht hast!

Für die Implementierung hängt es davon ab, wie sqlalchemy mit Datenbankvarianten umgeht, die dies nicht unterstützen (ich kann dies im Moment nicht testen, aber es scheint, dass sqlalchemy einen Fehler auslöst (zB http://stackoverflow.com/questions/ 23886764/multiple-insert-statements-in-mssql-with-sqlalchemy).Auch wenn es zur Folge hat, dass viele Leute chunksize setzen müssen, ist dies in der Tat keine gute Idee, dies als Standard zu tun (es sei denn, wir setzen chunksize standardmäßig auf einen Wert).
Das Hinzufügen eines Schlüsselworts scheint also vielleicht besser zu sein.

@artemyk @mangecoeur @hayd @danielballan

Anscheinend hat SQLAlchemy ein Flag dialect.supports_multivalues_insert (siehe zB http://pydoc.net/Python/SQLAlchemy/0.8.3/sqlalchemy.sql.compiler/ , in anderen Versionen möglicherweise supports_multirow_insert genannt, https ://www.mail-archive.com/[email protected]/msg202880.html ).

Da dies das Potenzial hat, Einfügungen erheblich zu beschleunigen, und wir leicht nach Unterstützung suchen können, denke ich, dass wir es vielleicht standardmäßig tun und auch Chunksize auf einen Standardwert setzen könnten (z. B. 16-kb-Chunks ... nicht sicher, was ist zu groß in den meisten Situationen). Wenn die mehrzeilige Einfügung fehlschlägt, könnten wir eine Ausnahme auslösen, die vorschlägt, die Chunksize zu verringern?

Jetzt muss ich nur noch die SQLAlchemy-Leute davon überzeugen, supports_multivalues_insert auf SQL Server >2005 auf true zu setzen (ich habe es in den Code gehackt und es funktioniert gut, aber es ist standardmäßig nicht aktiviert).

Auf einer themenbezogeneren Anmerkung denke ich, dass die Chunksize schwierig sein könnte. In meinem MySQL-Setup (das ich wahrscheinlich so konfiguriert habe, dass es große Pakete zulässt) kann ich chunksize=5000 setzen, in meinem SQLServer-Setup war 500 zu groß, aber 100 hat gut funktioniert. Es ist jedoch wahrscheinlich wahr, dass die meisten Vorteile dieser Technik darin liegen, dass man von 1 Zeile auf einmal zu 100 statt von 100 zu 1000 übergeht.

Was wäre, wenn chunksize=None bedeutete: "Größe des Chunks adaptiv auswählen"? Versuchen Sie etwas wie 5000, 500, 50, 1. Benutzer könnten dies ausschalten, indem sie eine Chunksize angeben. Wenn der Overhead dieser Versuche zu groß ist, mag ich den Vorschlag von @maxgrenderjones : chunksize=10 is a better default than chunksize=1 .

Zu diesem letzten Kommentar " chunksize=10 ist ein besserer Standardwert als chunksize=1 " -> das ist nicht ganz richtig, denke ich. Die aktuelle Situation besteht darin, _eine_ Ausführungsanweisung auszuführen, die aus mehrzeiligen, einzeiligen Einfügeanweisungen besteht (was keine Chunkgröße von 1 ist), während chunksize=10 bedeuten würde, viele Ausführungsanweisungen mit jeweils einer mehrzeiligen Zeile auszuführen Einfügung.
Und ich weiß nicht, ob das unbedingt schneller geht, aber vieles hängt von der Situation ab. Zum Beispiel mit dem aktuellen Code und mit einer lokalen SQLite-Datenbank:

In [4]: engine = create_engine('sqlite:///:memory:') #, echo='debug')

In [5]: df = pd.DataFrame(np.random.randn(50000, 10))

In [6]: %timeit df.to_sql('test_default', engine, if_exists='replace')
1 loops, best of 3: 956 ms per loop

In [7]: %timeit df.to_sql('test_default', engine, if_exists='replace', chunksize=10)
1 loops, best of 3: 2.23 s per loop

Aber natürlich nutzt dies nicht die Multi-Row-Funktion

Wir haben herausgefunden, wie man einen Affenpatch macht – könnte für jemand anderen nützlich sein. Halten Sie diesen Code bereit, bevor Sie Pandas importieren.

from pandas.io.sql import SQLTable

def _execute_insert(self, conn, keys, data_iter):
    print "Using monkey-patched _execute_insert"
    data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
    conn.execute(self.insert_statement().values(data))

SQLTable._execute_insert = _execute_insert

Vielleicht können wir einfach damit beginnen, diese Funktion über ein neues Schlüsselwort multirow=True hinzuzufügen (vorerst mit einem Standardwert von False), und dann können wir später immer sehen, ob wir sie standardmäßig aktivieren können?

@maxgrenderjones @nhockham daran interessiert, eine PR zu machen, um dies hinzuzufügen?

@jorisvandenbossche Ich denke, es ist riskant, Keyword-Argumente hinzuzufügen, um bestimmte Leistungsprofile zu adressieren. Wenn Sie garantieren können, dass es in allen Fällen schneller ist (falls erforderlich, indem es die beste Methode basierend auf den Eingaben bestimmt), benötigen Sie überhaupt kein Flag.

Verschiedene DB-Setups können unterschiedliche Leistungsoptimierungen haben (unterschiedliche DB-Leistungsprofile, lokal vs. Netzwerk, großer Speicher vs. schnelle SSD usw. usw.), wenn Sie anfangen, Schlüsselwort-Flags für jede hinzuzufügen, wird es ein Durcheinander.

Ich würde vorschlagen, Unterklassen von SQLDatabase und SQLTable zu erstellen, um leistungsspezifische Implementierungen zu adressieren, sie würden über die objektorientierte API verwendet. Vielleicht könnte eine "Backend Switching"-Methode hinzugefügt werden, aber ehrlich gesagt ist die Verwendung der OO-API sehr einfach, so dass dies wahrscheinlich zu viel des Guten für einen bereits spezialisierten Anwendungsfall ist.

Ich habe eine solche Unterklasse zum Laden großer Datensätze in Postgres erstellt (es ist tatsächlich viel schneller, Daten in CSV zu speichern und dann die integrierten nicht standardmäßigen COPY FROM SQL-Befehle zu verwenden, als Einfügungen zu verwenden, siehe https://gist.github. com/mangecoeur/1fbd63d4758c2ba0c470#file-pandas_postgres-py). Um es zu benutzen, tun Sie einfach PgSQLDatabase(engine, <args>).to_sql(frame, name,<kwargs>)

Nur als Referenz habe ich versucht, den Code von @jorisvandenbossche (Post vom 3. Dezember) mit der Multirow-Funktion auszuführen. Es ist um einiges langsamer. Die Geschwindigkeitskompromisse hier sind also nicht trivial:

In [4]: engine = create_engine('sqlite:///:memory:') #, echo='debug')

In [5]: df = pd.DataFrame(np.random.randn(50000, 10))

In [6]: 

In [6]: %timeit df.to_sql('test_default', engine, if_exists='replace')
1 loops, best of 3: 1.05 s per loop

In [7]: 

In [7]: from pandas.io.sql import SQLTable

In [8]: 

In [8]: def _execute_insert(self, conn, keys, data_iter):
   ...:         data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
   ...:         conn.execute(self.insert_statement().values(data))
   ...:     

In [9]: SQLTable._execute_insert = _execute_insert

In [10]: 

In [10]: reload(pd)
Out[10]: <module 'pandas' from '/usr/local/lib/python2.7/site-packages/pandas/__init__.pyc'>

In [11]: 

In [11]: %timeit df.to_sql('test_default', engine, if_exists='replace', chunksize=10)
1 loops, best of 3: 9.9 s per loop

Außerdem stimme ich zu, dass das Hinzufügen von Keyword-Parametern riskant ist. Die Multirow-Funktion scheint jedoch ziemlich grundlegend zu sein. Außerdem ist „Monkey-Patching“ wahrscheinlich nicht robuster gegenüber API-Änderungen als Schlüsselwortparameter.

Es ist wie ich vermutet habe. Monkey Patching ist nicht die Lösung, die ich vorgeschlagen habe - vielmehr liefern wir eine Reihe von leistungsorientierten Unterklassen, die der informierte Benutzer über die OO-Schnittstelle verwenden kann (um zu vermeiden, dass die funktionale API mit zu vielen Optionen geladen wird).

-----Originale Nachricht-----
Von: „Artemy Kolchinsky“ [email protected]
Gesendet: ‎26.02.2015 17:13
An: „pydata/pandas“ [email protected]
Cc: "mangecoeur" jon. [email protected]
Betreff: Betreff: [Pandas] Verwenden Sie mehrzeilige Einfügungen für massive Beschleunigungen bei to_sqlover-Verbindungen mit hoher Latenz (#8953)

Nur als Referenz habe ich versucht, den Code von @jorisvandenbossche (Post vom 3. Dezember) mit der Multirow-Funktion auszuführen. Es ist um einiges langsamer. Die Geschwindigkeitskompromisse hier sind also nicht trivial:
In [4]: ​​engine = create_engine('sqlite:///:memory:') #, echo='debug')

In [5]: df = pd.DataFrame(np.random.randn(50000, 10))

In [6]:

In [6]: %timeit df.to_sql('test_default', engine, if_exists='replace')
1 Schleife, Best of 3: 1,05 s pro Schleife

In [7]:

In [7]: aus pandas.io.sql SQLTable importieren

In [8]:

In [8]: def _execute_insert(self, conn, keys, data_iter):
...: data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
...: conn.execute(self.insert_statement().values(data))
...:

In [9]: SQLTable._execute_insert = _execute_insert

In [10]:

In [10]: reload(pd)
Aus[10]:

In [11]:

In [11]: %timeit df.to_sql('test_default', engine, if_exists='replace', chunksize=10)
1 Schleife, Best of 3: 9,9 s pro Schleife
Außerdem stimme ich zu, dass das Hinzufügen von Keyword-Parametern riskant ist. Die Multirow-Funktion scheint jedoch ziemlich grundlegend zu sein. Außerdem ist „Monkey-Patching“ wahrscheinlich nicht robuster gegenüber API-Änderungen als Schlüsselwortparameter.

Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an.

Gemäß dem ursprünglichen Tickettitel glaube ich nicht, dass dieser Ansatz in allen Fällen vorzuziehen ist, also würde ich ihn nicht zum Standard machen. Ohne sie sind die Pandas to_sql jedoch für mich unbrauchbar, daher ist es wichtig genug, dass ich die Änderung weiterhin beantrage. (Es ist auch das erste, was ich ändere, wenn ich meine Pandas-Version aktualisiere). Was sinnvolle chunksize -Werte betrifft, glaube ich nicht, dass es einen echten n gibt, da die Paketgröße auf schwer vorhersehbare Weise davon abhängt, wie viele Spalten vorhanden sind (und was darin enthalten ist). . Leider schlägt SQLServer mit einer Fehlermeldung fehl, die völlig unabhängig aussieht (aber nicht der Fall ist), wenn Sie chunksize zu hoch setzen (was wahrscheinlich der Grund dafür ist, dass mehrzeilige Einfügungen nicht aktiviert werden, außer mit einem Patch in SQLAlchemy), aber es funktioniert gut mit mysql . Benutzer müssen möglicherweise experimentieren, um zu bestimmen, welcher Wert von n wahrscheinlich zu einer akzeptabel großen Paketgröße führt (unabhängig davon, was ihre unterstützende Datenbank ist). Wenn Pandas n gewählt haben, werden wir wahrscheinlich viel weiter unten in den Implementierungsdetails landen, als wir wollen (dh die entgegengesetzte Richtung von dem SQLALchemy-Ansatz mit maximal möglicher Abstraktion).

Kurz gesagt, meine Empfehlung wäre, es als Schlüsselwort hinzuzufügen, mit einigen hilfreichen Kommentaren zur Verwendung. Dies wäre nicht das erste Mal, dass ein Schlüsselwort verwendet wird, um eine Implementierung auszuwählen (siehe: http://pandas.pydata.org/pandas-docs/dev/generated/pandas.DataFrame.apply.html), aber das ist es vielleicht nicht. t das beste Beispiel, da ich nicht die geringste Ahnung habe, was raw= bedeutet, obwohl ich die Erklärung gelesen habe!

Ich habe festgestellt, dass es auch eine große Menge an Speicher verbraucht. So wie ein 1,6+ GB DataFrame mit etwa 700.000 Zeilen und 301 Spalten fast 34 GB beim Einfügen benötigt! Das ist wie übertrieben ineffizient. Irgendwelche Ideen, warum das so sein könnte? Hier ist ein Bildschirmclip:

image

Hallo Leute,
irgendwelche Fortschritte zu diesem Thema?

Ich versuche, ungefähr 200.000 Zeilen mit to_sql einzufügen, aber es dauert ewig und verbraucht eine riesige Menge an Speicher! Die Verwendung von chuncksize hilft beim Speicher, aber die Geschwindigkeit ist immer noch sehr langsam.

Mein Eindruck, wenn ich mir den MSSQL DBase-Trace anschaue, ist, dass das Einfügen tatsächlich zeilenweise durchgeführt wird.

Der einzig praktikable Ansatz besteht jetzt darin, in eine CSV-Datei in einem freigegebenen Ordner zu speichern und BULK INSERT zu verwenden. Aber es ist sehr nervig und unelegant!

@andreacassioli Sie können odo verwenden, um einen DataFrame über eine zwischengeschaltete CSV-Datei in eine SQL-Datenbank einzufügen. Siehe Laden von CSVs in SQL-Datenbanken .

Ich glaube nicht, dass Sie mit ODBC auch nur annähernd an die Leistung von BULK INSERT herankommen können.

@ostrokach danke, in der Tat verwende ich jetzt CSV-Dateien. Wenn ich in die Nähe kommen könnte, würde ich ein bisschen Zeit gegen Einfachheit eintauschen!

Ich dachte, das könnte jemandem helfen:
http://docs.sqlalchemy.org/en/latest/faq/performance.html#i -m-inserting-400-000-rows-with-the-orm-and-it-s-really-slow

@indera pandas verwendet nicht das ORM, nur sqlalchemy Core (was der dortige Dokumenteintrag für große Einfügungen vorschlägt)

Gibt es einen Konsens darüber, wie man dies in der Zwischenzeit umgehen kann? Ich füge mehrere Millionen Zeilen in Postgres ein und es dauert ewig. Ist CSV/odo der richtige Weg?

@russlamb Ein praktischer Weg, dieses Problem zu lösen, ist der Massen-Upload. Dies ist jedoch jemand DB-spezifisch, also hat odo Lösungen für postgresl (und kann mysql sein), denke ich. für etwas wie sqlserver müssen Sie dies selbst tun (IOW, Sie müssen es schreiben).

Für sqlserver habe ich den FreeTDS-Treiber (http://www.freetds.org/software.html und https://github.com/mkleehammer/pyodbc ) mit SQLAlchemy-Entitäten verwendet, was zu sehr schnellen Einfügungen führte (20.000 Zeilen pro Datenrahmen). :

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()


class DemographicEntity(Base):
    __tablename__ = 'DEMOGRAPHIC'

    patid = db.Column("PATID", db.Text, primary_key=True)
    """
    patid = db.Column("PATID", db.Text, primary_key=True, autoincrement=False, nullable=True)
    birth_date = db.Column("BIRTH_DATE", db.Date)
    birth_time = db.Column("BIRTH_TIME", db.Text(5))
    sex = db.Column("SEX", db.Text(2))

def get_db_url(db_host, db_port, db_name, db_user, db_pass):
    params = parse.quote(
        "Driver={{FreeTDS}};Server={};Port={};"
        "Database={};UID={};PWD={};"
        .format(db_host, db_port, db_name, db_user, db_pass))
    return 'mssql+pyodbc:///?odbc_connect={}'.format(params)

def get_db_pool():
    """
    Create the database engine connection.
    <strong i="6">@see</strong> http://docs.sqlalchemy.org/en/latest/core/engines.html

    :return: Dialect object which can either be used directly
            to interact with the database, or can be passed to
            a Session object to work with the ORM.
    """
    global DB_POOL

    if DB_POOL is None:
        url = get_db_url(db_host=DB_HOST, db_port=DB_PORT, db_name=DB_NAME,
                         db_user=DB_USER, db_pass=DB_PASS)
        DB_POOL = db.create_engine(url,
                                   pool_size=10,
                                   max_overflow=5,
                                   pool_recycle=3600)

    try:
        DB_POOL.execute("USE {db}".format(db=DB_NAME))
    except db.exc.OperationalError:
        logger.error('Database {db} does not exist.'.format(db=DB_NAME))

    return DB_POOL


def save_frame():
    db_pool = get_db_pool()
    records = df.to_dict(orient='records')
    result = db_pool.execute(entity.__table__.insert(), records)

Ist CSV/odo der richtige Weg?

Diese Lösung wird meiner Meinung nach fast immer schneller sein, unabhängig von den Einstellungen für mehrere Zeilen / Chunksize.

Aber, @russlamb , es ist immer interessant zu hören, ob ein solches mehrzeiliges Keyword in Ihrem Fall eine Verbesserung wäre. Siehe zB https://github.com/pandas-dev/pandas/issues/8953#issuecomment -76139975 für eine Möglichkeit, dies einfach zu testen.

Ich denke, es besteht Einigkeit darüber, dass wir eine Möglichkeit haben möchten, dies zu spezifizieren (ohne notwendigerweise die Standardeinstellung zu ändern). Wenn also jemand eine PR dafür machen möchte, ist das sicherlich willkommen.
Es gab nur einige Diskussionen darüber, wie man diese Fähigkeit hinzufügt (neues Schlüsselwort vs. Unterklasse mit OO-API).

@jorisvandenbossche Das Dokument, das ich oben verlinkt habe, erwähnt: „Alternativ bietet das SQLAlchemy-ORM die Bulk Operations-Suite von Methoden, die Hooks in Unterabschnitte des Arbeitseinheitsprozesses bereitstellen, um INSERT- und UPDATE-Konstrukte auf Kernebene mit einem geringen Grad an ORM auszugeben -basierte Automatisierung."

Was ich vorschlage, ist, eine sqlserver-spezifische Version für to_sql zu implementieren, die unter der Haube die SQLAlchemy-ORMs für Beschleunigungen verwendet, wie in dem Code, den ich oben gepostet habe.

Dies wurde zuvor vorgeschlagen. Der Weg, den Sie gehen, ist, ein Pandas-SQL zu implementieren
Klasse optimiert für ein Backend. Ich habe in der Vergangenheit einen Gist zur Verwendung gepostet
postgres COPY FROM-Befehl, der viel schneller ist. Allerdings etwas ähnliches
ist jetzt in odo verfügbar und robuster aufgebaut. Es gibt nicht viel
Punkt IMHO beim Duplizieren der Arbeit von odo.

Am 7. März 2017 um 00:53 Uhr schrieb „Andrei Sura“ [email protected] :

@jorisvandenbossche https://github.com/jorisvandenbossche Das Dokument
Ich habe oben verlinkt: "Alternativ bietet das SQLAlchemy ORM die Bulk
Operations Suite von Methoden, die Hooks zu Unterabschnitten von bereitstellen
Einheit des Arbeitsprozesses, um INSERT und UPDATE auf Kernebene auszugeben
Konstrukte mit einem geringen Maß an ORM-basierter Automatisierung."

Was ich vorschlage, ist die Implementierung einer sqlserver-spezifischen Version für
"to_sql", das unter der Haube den SQLAlchemy-Kern für Beschleunigungen verwendet.


Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/pandas-dev/pandas/issues/8953#issuecomment-284437587 ,
oder den Thread stumm schalten
https://github.com/notifications/unsubscribe-auth/AAtYVDXKLuTlsh9ycpMQvU5C0hs_RxuYks5rjCwBgaJpZM4DCjLh
.

Beachten Sie auch, dass Sie erwähnt haben, dass sqlalchemy stattdessen kernen könnte. Es sei denn etwas
hat sich viel geändert, es wird sowieso nur sqlalchemy core verwendet, kein orm. Wenn du
Wenn Sie mehr beschleunigen möchten als mit Core, müssen Sie auf eine niedrigere Ebene gehen, db
spezifische Optimierung

Am 7. März 2017 um 00:53 Uhr schrieb „Andrei Sura“ [email protected] :

@jorisvandenbossche https://github.com/jorisvandenbossche Das Dokument
Ich habe oben verlinkt: "Alternativ bietet das SQLAlchemy ORM die Bulk
Operations Suite von Methoden, die Hooks zu Unterabschnitten von bereitstellen
Einheit des Arbeitsprozesses, um INSERT und UPDATE auf Kernebene auszugeben
Konstrukte mit einem geringen Maß an ORM-basierter Automatisierung."

Was ich vorschlage, ist die Implementierung einer sqlserver-spezifischen Version für
"to_sql", das unter der Haube den SQLAlchemy-Kern für Beschleunigungen verwendet.


Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/pandas-dev/pandas/issues/8953#issuecomment-284437587 ,
oder den Thread stumm schalten
https://github.com/notifications/unsubscribe-auth/AAtYVDXKLuTlsh9ycpMQvU5C0hs_RxuYks5rjCwBgaJpZM4DCjLh
.

Wird das behoben/bearbeitet? Ab sofort ist das Einfügen von Pandas-Datenrahmen in eine SQL-Datenbank extrem langsam, es sei denn, es handelt sich um einen Spielzeug-Datenrahmen. Lassen Sie uns eine Lösung finden und vorantreiben?

@dfernan Wie oben erwähnt, möchten Sie sich vielleicht den odo ansehen . Die Verwendung einer zwischengeschalteten CSV-Datei wird immer um Größenordnungen schneller sein als das Durchlaufen von sqlalchemy, egal welche Art von Verbesserungen hier passieren ...

@ostrokach , ich bin nicht davon überzeugt, dass das Verhalten von odo dem entspricht, was der typische Pandas-Benutzer will. Mehrzeilige Einfügungen über ODBC sind für die meisten Analysten wahrscheinlich schnell genug.

Um für mich selbst zu sprechen, habe ich gerade ein paar Stunden damit verbracht, vom Affenbeet oben zu Odo zu wechseln. Die normale Pandas-Laufzeit betrug mehr als 10 Stunden, RBAR. Der Monkey-Patch läuft in 2 auf demselben Datensatz.
Die odo/CSV-Route war erwartungsgemäß schneller, aber nicht ausreichend, um den Aufwand wert zu sein. Ich habe mich mit CSV-Konvertierungsproblemen beschäftigt, die mir nicht viel ausmachten, alles im Namen der Vermeidung des Affenpatches. Ich importiere 250.000 Zeilen aus ~ 10 MySQL- und PG-DBs in einen gemeinsamen Bereich in Postgres für die NLP-Analyse.

Ich bin mit den Bulk-Loading-Ansätzen, die Odo unterstützt, bestens vertraut. Ich benutze sie seit Jahren, wo ich mit CSV-Daten beginne. Es gibt wichtige Einschränkungen für sie:

  1. Für den Fall df->CSV->Postgres sind Shell-Zugriff und ein scp-Schritt erforderlich, um die CSV auf dem PG-Host abzurufen. Es sieht so aus, als hätte @mangecoeur dies mit einem Stream zu STDIN umgangen.
  2. Für meinen Zweck (250.000 Kommentarzeilen mit vielen Sonderfällen im Textinhalt) hatte ich Mühe, die CSV-Parameter richtig zu machen. Ich wollte Leistungssteigerungen nicht dringend genug, um weiter in diese zu investieren.

Ich wechsle zurück zum Patch, damit ich mich an die Analysearbeit machen kann.

Ich stimme @jorisvandenbossche , @maxgrenderjones zu. Eine Option (kein Standard), um dies auszuwählen, wäre immens nützlich. Der Punkt von @artemyk über dialect.supports_multivalues_insert könnte dies sogar zu einem vernünftigen Standard machen.

Ich bin froh, eine PR einzureichen, wenn das dies voranbringen würde.

Nur um meine Erfahrung mit Odo hinzuzufügen, es hat wegen bekannter Probleme mit der Codierung nicht für MS SQL-Masseneinfügungen funktioniert. imho m-row insert sind eine gute praktische lösung für die meisten ppl.

@markschwarz eine Option, damit dies schneller funktioniert, wäre sehr willkommen!

Wenn ich die Abfragen mit sqlite nachverfolge, scheine ich mehrere Einfügungen zu erhalten, wenn ich chunksize verwende:

2017-09-28 00:21:39,007 INFO sqlalchemy.engine.base.Engine INSERT INTO country_hsproduct_year (location_id, product_id, year, export_rca, import_value, cog, export_value, distance, location_level, product_level) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2017-09-28 00:21:39,007 INFO sqlalchemy.engine.base.Engine ((75, 1237, 1996, 1.7283086776733398, 273487116.0, 0.0, 514320160.0, 0.5413745641708374, 'country', '4digit'), (75, 1237, 1997, 1.7167805433273315, 312047528.0, 0.0, 592372864.0, 0.5314807891845703, 'country', '4digit'), (75, 1237, 1998, 1.2120152711868286, 341676961.0, 0.0, 468860608.0, 0.5472233295440674, 'country', '4digit'), (75, 1237, 1999, 1.236651062965393, 334604240.0, 0.0, 440722336.0, 0.5695921182632446, 'country', '4digit'), (75, 1237, 2000, 1.189828872680664, 383555023.0, 0.0, 426384832.0, 0.5794379711151123, 'country', '4digit'), (75, 1237, 2001, 0.9920380115509033, 374157144.0, 0.3462945520877838, 327031392.0, 0.6234743595123291, 'country', '4digit'), (75, 1237, 2002, 1.0405025482177734, 471456583.0, 0.0, 377909376.0, 0.6023964285850525, 'country', '4digit'), (75, 1237, 2003, 1.147829532623291, 552441401.0, 0.0, 481313504.0, 0.5896202325820923, 'country', '4digit')  ... displaying 10 of 100000 total bound parameter sets ...  (79, 1024, 2015, 0.0, None, 0.8785018920898438, 0.0, 0.9823430776596069, 'country', '4digit'), (79, 1025, 1995, 0.0, None, 0.5624096989631653, 0.0, 0.9839603304862976, 'country', '4digit'))

(also ohne den Monkey Patch)

Interessanterweise bricht es mit dem Affenpatch, wenn ich ihm eine Chunksize von 10 ^ 5 gebe, aber nicht 10 ^ 3. Der Fehler ist "zu viele SQL-Variablen" auf SQLite.

@makmanalp , ich habe PG noch nicht nachverfolgt, um dieses Verhalten zu überprüfen, aber ich habe fast immer chunksize beim Einfügen festgelegt. In meinem obigen Beispiel habe ich es zufällig auf 5 Werte zwischen 200-5000 gesetzt. Ich habe keine drastischen Unterschiede in der verstrichenen Zeit zwischen diesen Optionen gesehen, ohne den Monkey Patch. Mit dem Patch sank die verstrichene Zeit um ~80 %.

https://github.com/pandas-dev/pandas/issues/8953#issuecomment -76139975

Funktioniert dieser Affenpatch noch? Ich habe es auf MS SQL Server ausprobiert, aber keine Verbesserung festgestellt. Außerdem wird eine Ausnahme ausgelöst:

(pyodbc.Error) ('07002', '[07002] [Microsoft][SQL Server Native Client 11.0]COUNT field incorrect or syntax error (0) (SQLExecDirectW)')

@hangyao Ich denke, dass der Patch implementierungsspezifisch ist, eines der Dinge, die Python DBAPI dem DBAPI-Treiber überlässt, wie er damit umgehen soll. Es könnte also schneller sein oder nicht. RE: der Syntaxfehler, darüber bin ich mir nicht sicher.

Ich denke darüber nach, die folgende Funktion in der /io/sql.py -Datei in Zeile 507 innerhalb der _engine_builder -Funktion in einer neuen IF-Klausel in Zeile 521 hinzuzufügen, wodurch das 'neue' _engine_builder das Folgende wird Ausschnitt. Ich habe es kurz in meiner Umgebung getestet und es funktioniert hervorragend für MSSQL-Datenbanken und erreicht eine >100-fache Beschleunigung. Ich habe es noch nicht auf anderen Datenbanken getestet.

Die Sache, mich davon abzuhalten, eine PR zu machen, ist, dass es meiner Meinung nach mehr Aufwand ist, es sauber und sicher zu machen, als es einfach wie unten einzufügen. Dies ist möglicherweise nicht immer die gewünschte Spezifikation und das Hinzufügen eines booleschen Schalters, der diese Einstellung ein- / ausschaltet , (z. B. fast_executemany=True ) in to_sql schien mir ein etwas zu großer Aufwand, um einfach darauf zu verzichten, denke ich.

Also meine Fragen sind:

  • Funktioniert die folgende Funktion und erhöht auch die INSERT-Geschwindigkeit für PostgreSQL?

  • Möchte Pandas Event dieses Snippet in seiner Quelle haben? Wenn ja:

  • Ist es erwünscht, diese Funktion zur standardmäßigen sql.py -Funktionalität hinzuzufügen, oder gibt es einen besseren Ort, um dies hinzuzufügen?

Freue mich über ein paar Kommentare.

Quelle für die Antwort: https://stackoverflow.com/questions/48006551/speeding-up-pandas-dataframe-to-sql-with-fast-executemany-of-pyodbc/48861231#48861231

@event.listens_for(engine, 'before_cursor_execute')
def receive_before_cursor_execute(conn, cursor, statement, params, context, executemany):
    if executemany:
        cursor.fast_executemany = True
def _engine_builder(con):
    """
    Returns a SQLAlchemy engine from a URI (if con is a string)
    else it just return con without modifying it.
    """
    global _SQLALCHEMY_INSTALLED
    if isinstance(con, string_types):
        try:
            import sqlalchemy
        except ImportError:
            _SQLALCHEMY_INSTALLED = False
        else:
            con = sqlalchemy.create_engine(con)

    @event.listens_for(engine, 'before_cursor_execute')
    def receive_before_cursor_execute(conn, cursor, statement, params, context, executemany):
        if executemany:
            cursor.fast_executemany = True
return con

@tsktsktsk123 Kürzlich wurde diesbezüglich eine PR zusammengeführt: https://github.com/pandas-dev/pandas/pull/19664. Ich habe mir Ihren Beitrag noch nicht im Detail angesehen, und es ist sicherlich nicht genau dasselbe (es verwendet das Attribut supports_multivalues_insert der sqlalchemy-Engine), aber nur um sicherzustellen, dass Sie sich dessen bewusst sind, falls dies bereits hilft auch.

Das sind tolle Nachrichten! Ich habe mir die PR nicht angesehen, werde sie aber dieses Wochenende vergleichen und mich mit den Ergebnissen zurückmelden. Danke für die Warnung.

Ich habe gerade 0.23.0 RC2 (auf postgresql) ausprobiert und anstatt eine Leistungssteigerung zu erzielen, wurde mein Skript erheblich langsamer. Die DB-Abfrage wurde viel schneller, aber beim Messen der Zeit für to_sql() wurde sie tatsächlich bis zu 1,5-mal langsamer (wie von 7 auf 11 Sekunden) ...

Ich bin mir nicht sicher, ob die Verlangsamung von diesem PR kommt, da ich gerade den RC getestet habe.

Hat noch jemand das gleiche Problem erlebt?

@schettino72 Wie viele Daten hast du eingefügt?

Etwa 30.000 Zeilen mit 10 Spalten. Aber wirklich fast alles, was ich versuche, ist langsamer (SQL ist schneller, aber insgesamt langsamer). Es wird eine riesige SQL-Anweisung erstellt, in der für JEDEN Wert eine Wertinterpolation vorhanden ist. Etwas wie

 %(user_id_m32639)s, %(event_id_m32639)s, %(colx_m32639)s,

Ich fand d6tstack viel einfacher zu verwenden, es ist ein Einzeiler d6tstack.utils.pd_to_psql(df, cfg_uri_psql, 'benchmark', if_exists='replace') und es ist viel schneller als df.to_sql() . Unterstützt postgres und mysql. Siehe https://github.com/d6t/d6tstack/blob/master/examples-sql.ipynb

Ich habe die Monkey Patch-Lösung verwendet:

from pandas.io.sql import SQLTable

def _execute_insert(self, conn, keys, data_iter):
    print "Using monkey-patched _execute_insert"
    data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
    conn.execute(self.insert_statement().values(data))

SQLTable._execute_insert = _execute_insert

seit einiger Zeit, aber jetzt bekomme ich eine Fehlermeldung:

TypeError: insert_statement() missing 2 required positional arguments: 'data' and 'conn'

Bekommt das noch jemand? Ich bin auf Python 3.6.5 (Anaconda) und pandas==0.23.0

wird das behoben? Derzeit ist df.to_sql extrem langsam und kann für viele praktische Anwendungsfälle überhaupt nicht verwendet werden. Das Odo-Projekt scheint bereits aufgegeben worden zu sein.
Ich habe folgende Anwendungsfälle in Finanzzeitreihen, in denen df.to_sql so gut wie nicht verwendbar ist:
1) Kopieren historischer CSV-Daten in die Postgres-Datenbank - kann df.to_sql nicht verwenden und musste benutzerdefinierten Code um die psycopg2 copy_from-Funktionalität herum verwenden
2) Streaming-Daten (die in einem Stapel von ~ 500-3000 Zeilen pro Sekunde ankommen), die in die Postgres-Datenbank ausgegeben werden sollen - auch hier ist die Leistung von df.to_sql ziemlich enttäuschend, da es zu lange dauert, diese natürlichen Datenstapel in Postgres einzufügen.
Der einzige Ort, an dem ich df.to_sql jetzt nützlich finde, ist das automatische Erstellen von Tabellen !!! - was nicht der Anwendungsfall ist, für den es entwickelt wurde.
Ich bin mir nicht sicher, ob andere Leute die gleichen Bedenken teilen, aber dieses Problem erfordert etwas Aufmerksamkeit, damit die Schnittstellen "Datenrahmen zu Datenbank" reibungslos funktionieren.
Freuen.

Hey, ich erhalte diesen Fehler, wenn ich versuche, eine Mehrfacheinfügung in eine SQLite-Datenbank durchzuführen:

Das ist mein Code:
df.to_sql("financial_data", con=conn, if_exists="append", index=False, method="multi")

und ich bekomme diesen Fehler:

Traceback (most recent call last):

  File "<ipython-input-11-cf095145b980>", line 1, in <module>
    handler.insert_financial_data_from_df(data, "GOOG")

  File "C:\Users\user01\Documents\Code\FinancialHandler.py", line 110, in insert_financial_data_from_df
    df.to_sql("financial_data", con=conn, if_exists="append", index=False, method="multi")

  File "C:\Users\user01\AppData\Local\Continuum\anaconda3\lib\site-packages\pandas\core\generic.py", line 2531, in to_sql
    dtype=dtype, method=method)

  File "C:\Users\user01\AppData\Local\Continuum\anaconda3\lib\site-packages\pandas\io\sql.py", line 460, in to_sql
    chunksize=chunksize, dtype=dtype, method=method)

  File "C:\Users\user01\AppData\Local\Continuum\anaconda3\lib\site-packages\pandas\io\sql.py", line 1547, in to_sql
    table.insert(chunksize, method)

  File "C:\Users\user01\AppData\Local\Continuum\anaconda3\lib\site-packages\pandas\io\sql.py", line 686, in insert
    exec_insert(conn, keys, chunk_iter)

  File "C:\Users\user01\AppData\Local\Continuum\anaconda3\lib\site-packages\pandas\io\sql.py", line 609, in _execute_insert_multi
    conn.execute(self.table.insert(data))

TypeError: insert() takes exactly 2 arguments (1 given)

Warum passiert dies? Ich verwende Python 3.7.3 (Anaconda), pandas 0.24.2 und sqlite3 2.6.0.

Vielen Dank im Voraus!

@jconstanzo kannst du das als neues Thema öffnen?
Und wenn möglich, können Sie versuchen, ein reproduzierbares Beispiel zu liefern? (z. B. ein kleiner Beispieldatenrahmen, der das Problem zeigen kann)

@jconstanzo Habe hier das gleiche Problem. Die Verwendung method='multi' (in meinem Fall in Kombination mit chunksize ) scheint diesen Fehler auszulösen, wenn Sie versuchen, in eine SQLite-Datenbank einzufügen.

Leider kann ich nicht wirklich einen Beispieldatenrahmen liefern, da mein Datensatz riesig ist, das ist der Grund, warum ich überhaupt method und chunksize verwende.

Die Verspätung tut mir leid. Ich habe gerade ein Issue für dieses Problem eröffnet: https://github.com/pandas-dev/pandas/issues/29921

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen