Existe-t-il un moyen pour SQLAlchemy de faire une insertion en bloc plutôt que d'insérer chaque objet individuel. c'est à dire,
Faire:
INSERT INTO `foo` (`bar`) VALUES (1), (2), (3)
plutôt que:
INSERT INTO `foo` (`bar`) VALUES (1)
INSERT INTO `foo` (`bar`) VALUES (2)
INSERT INTO `foo` (`bar`) VALUES (3)
Je viens de convertir du code pour utiliser sqlalchemy plutôt que sql brut et bien qu'il soit maintenant beaucoup plus agréable de travailler avec, il semble être plus lent maintenant (jusqu'à un facteur de 10), je me demande si c'est la raison.
Peut-être pourrais-je améliorer la situation en utilisant des sessions plus efficacement. Pour le moment, je l'ai fait autoCommit=False
et session.commit()
j'ai ajouté quelques trucs. Bien que cela semble rendre les données obsolètes si la base de données est modifiée ailleurs, comme même si je fais une nouvelle requête, je récupère toujours les anciens résultats?
Merci de votre aide!
Réponses:
SQLAlchemy a introduit cela dans la version
1.0.0
:Opérations en masse - SQLAlchemy Docs
Avec ces opérations, vous pouvez désormais effectuer des insertions ou des mises à jour en masse!
Par exemple, vous pouvez faire:
Ici, une insertion en vrac sera faite.
la source
\copy
avec psql (du même client vers le même serveur), je constate une énorme différence de performances côté serveur, ce qui entraîne environ 10 fois plus d'insert / s. Apparemment, le chargement en masse en utilisant\copy
(ouCOPY
sur le serveur) en utilisant un emballage pour communiquer de client à serveur est BEAUCOUP mieux que d'utiliser SQL via SQLAlchemy. Plus d' infos: Grande différence d' insertion en bloc de performance PostgreSQL vs ... .Les documents sqlalchemy ont un aperçu des performances de diverses techniques pouvant être utilisées pour les insertions en masse:
la source
Autant que je sache, il n'y a aucun moyen pour que l'ORM émette des insertions en masse. Je crois que la raison sous-jacente est que SQLAlchemy doit garder une trace de l'identité de chaque objet (c'est-à-dire, les nouvelles clés primaires), et les insertions en masse interfèrent avec cela. Par exemple, en supposant que votre
foo
table contient uneid
colonne et est mappée à uneFoo
classe:Puisque SQLAlchemy a récupéré la valeur pour
x.id
sans émettre une autre requête, nous pouvons en déduire qu'il a obtenu la valeur directement à partir de l'INSERT
instruction. Si vous n'avez pas besoin d'un accès ultérieur aux objets créés via les mêmes instances, vous pouvez ignorer la couche ORM pour votre insertion:SQLAlchemy ne peut pas faire correspondre ces nouvelles lignes avec des objets existants, vous devrez donc les interroger à nouveau pour les opérations suivantes.
En ce qui concerne les données obsolètes, il est utile de se rappeler que la session n'a aucun moyen intégré de savoir quand la base de données est modifiée en dehors de la session. Pour accéder aux données modifiées en externe via des instances existantes, les instances doivent être marquées comme expirées . Cela se produit par défaut sur
session.commit()
, mais peut être fait manuellement en appelantsession.expire_all()
ousession.expire(instance)
. Un exemple (SQL omis):session.commit()
expirex
, donc la première instruction print ouvre implicitement une nouvelle transaction et ré-interrogex
les attributs de. Si vous mettez en commentaire la première instruction d'impression, vous remarquerez que la seconde prend maintenant la valeur correcte, car la nouvelle requête n'est émise qu'après la mise à jour.Cela a du sens du point de vue de l'isolation transactionnelle - vous ne devez prendre en compte que les modifications externes entre les transactions. Si cela vous cause des problèmes, je vous suggère de clarifier ou de repenser les limites de transaction de votre application au lieu de chercher immédiatement
session.expire_all()
.la source
autocommit=False
, je pense que vous devriez appelersession.commit()
à la fin de la demande (je ne suis pas familier avec TurboGears, alors ignorez cela si cela est géré pour vous au niveau du cadre). En plus de vous assurer que vos modifications ont été apportées à la base de données, cela expirerait tout dans la session. La prochaine transaction ne commencerait pas avant la prochaine utilisation de cette session, donc les futures demandes sur le même thread ne verront pas les données périmées.session.execute(Foo.__table__.insert(), values)
Je le fais habituellement en utilisant
add_all
.la source
.add
les insérer à la session une à la fois?Add the given collection of instances to this Session.
avez-vous des raisons de croire qu'elle ne fait pas d'insertion en masse?.add
chaque élément individuellement.bulk_save_objects()
, avec aflush()
, nous pouvons obtenir l'ID de l'objet, maisbulk_save_objects()
pas (événement avecflush()
appelé).Le support direct a été ajouté à SQLAlchemy à partir de la version 0.8
Selon la documentation ,
connection.execute(table.insert().values(data))
devrait faire l'affaire. (Notez que ce n'est pas la même chose queconnection.execute(table.insert(), data)
ce qui entraîne de nombreuses insertions de lignes individuelles via un appel àexecutemany
). Sur tout sauf une connexion locale, la différence de performances peut être énorme.la source
SQLAlchemy a introduit cela dans la version
1.0.0
:Opérations en masse - SQLAlchemy Docs
Avec ces opérations, vous pouvez désormais effectuer des insertions ou des mises à jour en masse!
Par exemple (si vous voulez la plus faible surcharge pour les INSERT de table simples), vous pouvez utiliser
Session.bulk_insert_mappings()
:Ou, si vous le souhaitez, ignorez les
loadme
tuples et écrivez les dictionnaires directement dansdicts
(mais je trouve plus facile de laisser toute la verbosité des données et de charger une liste de dictionnaires en boucle).la source
La réponse de Piere est correcte, mais un problème est que,
bulk_save_objects
par défaut, ne renvoie pas les clés primaires des objets, si cela vous concerne. Réglezreturn_defaults
surTrue
pour obtenir ce comportement.La documentation est ici .
la source
Toutes les routes mènent à Rome , mais certaines d'entre elles traversent des montagnes, nécessitent des ferries, mais si vous voulez vous y rendre rapidement, prenez simplement l'autoroute.
Dans ce cas, l'autoroute doit utiliser la fonction execute_batch () de psycopg2 . La documentation le dit le mieux:
La mise en œuvre actuelle de
executemany()
(en utilisant un euphémisme extrêmement charitable) n'est pas particulièrement performante. Ces fonctions peuvent être utilisées pour accélérer l'exécution répétée d'une instruction par rapport à un ensemble de paramètres. En réduisant le nombre d’allers-retours du serveur, les performances peuvent être bien meilleures que l’utilisationexecutemany()
.Dans mon propre test,
execute_batch()
c'est environ deux fois plus rapide queexecutemany()
, et donne la possibilité de configurer le page_size pour un peaufinage supplémentaire (si vous voulez extraire les derniers 2-3% des performances du pilote).La même fonctionnalité peut facilement être activée si vous utilisez SQLAlchemy en définissant
use_batch_mode=True
comme paramètre lorsque vous instanciez le moteur aveccreate_engine()
la source
execute_values
est plus rapide que psycopg2execute_batch
lors de l'insertion groupée !C'est un moyen:
Cela va insérer comme ceci:
Référence: La FAQ SQLAlchemy inclut des benchmarks pour diverses méthodes de commit.
la source
La meilleure réponse que j'ai trouvée jusqu'à présent était dans la documentation de sqlalchemy:
http://docs.sqlalchemy.org/en/latest/faq/performance.html#im-inserting-400-000-rows-with-the-orm-and-it-s-really-slow
Il existe un exemple complet de benchmark de solutions possibles.
Comme indiqué dans la documentation:
bulk_save_objects n'est pas la meilleure solution mais ses performances sont correctes.
Je pense que la deuxième meilleure implémentation en termes de lisibilité était avec SQLAlchemy Core:
Le contexte de cette fonction est donné dans l'article de documentation.
la source