Gibt es eine Möglichkeit, SQLAlchemy dazu zu bringen, eine Masseneinfügung durchzuführen, anstatt jedes einzelne Objekt einzufügen? dh
tun:
INSERT INTO `foo` (`bar`) VALUES (1), (2), (3)
eher, als:
INSERT INTO `foo` (`bar`) VALUES (1)
INSERT INTO `foo` (`bar`) VALUES (2)
INSERT INTO `foo` (`bar`) VALUES (3)
Ich habe gerade Code konvertiert, um SQLalchemie anstelle von Roh-SQL zu verwenden, und obwohl es jetzt viel schöner ist, damit zu arbeiten, scheint es jetzt langsamer zu sein (bis zu einem Faktor von 10), frage ich mich, ob dies der Grund ist.
Vielleicht könnte ich die Situation durch effizientere Sitzungen verbessern. Im Moment habe autoCommit=False
und mache session.commit()
ich ein, nachdem ich ein paar Sachen hinzugefügt habe. Obwohl dies dazu zu führen scheint, dass die Daten veraltet sind, wenn die Datenbank an einer anderen Stelle geändert wird, wie auch wenn ich eine neue Abfrage durchführe, erhalte ich immer noch alte Ergebnisse zurück?
Danke für Ihre Hilfe!
Antworten:
SQLAlchemy hat das in der Version eingeführt
1.0.0
:Massenoperationen - SQLAlchemy-Dokumente
Mit diesen Vorgängen können Sie jetzt Masseneinfügungen oder Aktualisierungen vornehmen!
Zum Beispiel können Sie Folgendes tun:
Hier wird ein Bulk-Einsatz gemacht.
quelle
\copy
psql (vom selben Client zum selben Server) sehe ich auf der Serverseite einen großen Leistungsunterschied, der zu etwa 10x mehr Einfügungen / s führt. Anscheinend ist das Massenladen mithilfe\copy
(oderCOPY
auf dem Server) mithilfe eines Pakets bei der Kommunikation von Client zu Server viel besser als die Verwendung von SQL über SQLAlchemy. Weitere Informationen: Großer Masseneinsatz Performance - Unterschied PostgreSQL vs ... .In den sqlalchemy-Dokumenten wird die Leistung verschiedener Techniken beschrieben, die für Masseneinfügungen verwendet werden können:
quelle
Soweit ich weiß, gibt es keine Möglichkeit, das ORM dazu zu bringen, Masseneinsätze auszustellen. Ich glaube, der Grund dafür ist, dass SQLAlchemy die Identität jedes Objekts (dh neue Primärschlüssel) verfolgen muss, und Bulk-Einfügungen stören dies. Angenommen, Ihre
foo
Tabelle enthält eineid
Spalte und ist einerFoo
Klasse zugeordnet:Da SQLAlchemy den Wert für
x.id
ohne weitere Abfrage ermittelt hat, können wir daraus schließen, dass der Wert direkt aus derINSERT
Anweisung stammt. Wenn Sie keinen späteren Zugriff auf die erstellten Objekte über dieselben Instanzen benötigen , können Sie die ORM-Ebene für Ihre Einfügung überspringen:SQLAlchemy kann diese neuen Zeilen nicht mit vorhandenen Objekten abgleichen, daher müssen Sie sie für nachfolgende Vorgänge erneut abfragen.
In Bezug auf veraltete Daten ist es hilfreich, sich daran zu erinnern, dass die Sitzung keine integrierte Methode enthält, um festzustellen, wann die Datenbank außerhalb der Sitzung geändert wird. Um über vorhandene Instanzen auf extern geänderte Daten zugreifen zu können, müssen die Instanzen als abgelaufen markiert werden . Dies geschieht standardmäßig am
session.commit()
, kann jedoch manuell durch Aufrufen vonsession.expire_all()
oder erfolgensession.expire(instance)
. Ein Beispiel (SQL weggelassen):session.commit()
läuft abx
, sodass die erste print-Anweisung implizit eine neue Transaktion öffnet und erneut abfragtx
die Attribute erneut ab. Wenn Sie die erste Druckanweisung auskommentieren, werden Sie feststellen, dass die zweite jetzt den richtigen Wert aufnimmt, da die neue Abfrage erst nach der Aktualisierung ausgegeben wird.Dies ist unter dem Gesichtspunkt der Transaktionsisolation sinnvoll - Sie sollten nur externe Änderungen zwischen Transaktionen vornehmen. Wenn dies zu Problemen führt, würde ich vorschlagen, die Transaktionsgrenzen Ihrer Anwendung zu klären oder zu überdenken, anstatt sofort danach zu greifen
session.expire_all()
.quelle
autocommit=False
, glaube ich Sie anrufen solltesession.commit()
auf Anfrage Abschluss (I mit Turbogears nicht vertraut bin, das so ignorieren , wenn das für Sie auf Framework - Ebene behandelt wird ). Abgesehen davon, dass Ihre Änderungen in die Datenbank gelangt sind, läuft alles in der Sitzung ab. Die nächste Transaktion würde erst bei der nächsten Verwendung dieser Sitzung beginnen, sodass zukünftige Anforderungen im selben Thread keine veralteten Daten sehen.session.execute(Foo.__table__.insert(), values)
Ich mache es normalerweise mit
add_all
.quelle
.add
einzeln zur Sitzung zu bringen?Add the given collection of instances to this Session.
Haben Sie Grund zu der Annahme, dass keine Masseneinfügung erfolgt?.add
jeden Artikel einzeln zu sein.bulk_save_objects()
aflush()
, wir können die ID des Objekts abrufen, können es aberbulk_save_objects()
nicht (Ereignis mitflush()
aufgerufen).SQLAlchemy wurde ab Version 0.8 direkt unterstützt
Richtet sich nach den docs ,
connection.execute(table.insert().values(data))
sollte es tun. (Beachten Sie, dass dies nicht dasselbe ist,connection.execute(table.insert(), data)
was zu vielen einzelnen Zeileneinfügungen über einen Aufruf von führt.executemany
) Bei einer anderen als einer lokalen Verbindung kann der Leistungsunterschied enorm sein.quelle
SQLAlchemy hat das in der Version eingeführt
1.0.0
:Massenoperationen - SQLAlchemy-Dokumente
Mit diesen Vorgängen können Sie jetzt Masseneinfügungen oder Aktualisierungen vornehmen!
Zum Beispiel (wenn Sie den geringsten Overhead für einfache Tabellen-INSERTs wünschen) können Sie Folgendes verwenden
Session.bulk_insert_mappings()
:Oder, wenn Sie möchten, überspringen Sie die
loadme
Tupel und schreiben Sie die Wörterbücher direkt indicts
(aber ich finde es einfacher, die gesamte Worthaftigkeit aus den Daten herauszulassen und eine Liste der Wörterbücher in einer Schleife zu laden).quelle
Die Antwort von Piere ist richtig, aber ein Problem ist, dass
bulk_save_objects
standardmäßig nicht die Primärschlüssel der Objekte zurückgegeben werden, wenn dies für Sie von Belang ist. Setreturn_defaults
zuTrue
um dieses Verhalten zu erhalten.Die Dokumentation finden Sie hier .
quelle
Alle Wege führen nach Rom , aber einige von ihnen überqueren Berge, erfordern Fähren, aber wenn Sie schnell dorthin wollen, nehmen Sie einfach die Autobahn.
In diesem Fall muss die Autobahn die Funktion execute_batch () von psycopg2 verwenden . Die Dokumentation sagt es am besten:
Die derzeitige Implementierung von
executemany()
ist (unter Verwendung einer äußerst gemeinnützigen Untertreibung) nicht besonders leistungsfähig. Diese Funktionen können verwendet werden, um die wiederholte Ausführung einer Anweisung anhand einer Reihe von Parametern zu beschleunigen. Durch die Reduzierung der Anzahl der Server-Roundtrips kann die Leistung um Größenordnungen besser sein als bei Verwendungexecutemany()
.In meinem eigenen Test
execute_batch()
ist ungefähr doppelt so schnell wieexecutemany()
und bietet die Möglichkeit, die page_size für weitere Optimierungen zu konfigurieren (wenn Sie die letzten 2-3% der Leistung aus dem Treiber herausholen möchten).Dieselbe Funktion kann problemlos aktiviert werden, wenn Sie SQLAlchemy verwenden, indem
use_batch_mode=True
Sie beim Instanziieren der Engine als Parameter festlegencreate_engine()
quelle
execute_values
ist schneller als Psycopg2,execute_batch
wenn Bulk-Inserts ausgeführt werden!Dies ist ein Weg:
Dies wird wie folgt eingefügt:
Referenz: Die SQLAlchemy FAQ enthält Benchmarks für verschiedene Methoden begehen.
quelle
Die beste Antwort, die ich bisher gefunden habe, war in der sqlalchemy-Dokumentation:
http://docs.sqlalchemy.org/en/latest/faq/performance.html#im-inserting-400-000-rows-with-the-orm-and-it-s-really-slow
Es gibt ein vollständiges Beispiel für einen Benchmark möglicher Lösungen.
Wie in der Dokumentation gezeigt:
mass_save_objects ist nicht die beste Lösung, aber die Leistung ist korrekt.
Die zweitbeste Implementierung in Bezug auf die Lesbarkeit war meiner Meinung nach mit dem SQLAlchemy Core:
Der Kontext dieser Funktion ist im Dokumentationsartikel angegeben.
quelle