Psycopg2, Postgresql, Python: le moyen le plus rapide de bulk-insert
je cherche le moyen le plus efficace d'insérer des millions de tuples dans une base de données. J'utilise Python, PostgreSQL et psycopg2 .
j'ai créé une longue liste de tulpesqui doivent être insérés dans la base de données, parfois avec des modificateurs comme géométrique Simplify
.
la façon naïve de le faire serait de formater une liste de déclarations INSERT
, mais il y a trois autres méthodes que j'ai lu à propos de:
- à l'Aide
pyformat
liaison style pour paramétrique d'insertion - utilisant
executemany
sur la liste des tuples, et - en utilisant l'écriture des résultats dans un fichier et en utilisant
COPY
.
il semble que la première voie soit la plus efficace, mais j'apprécierais vos intuitions et vos bribes de code me disant comment le faire correctement.
8 réponses
Oui, je voterais pour la copie, à condition que vous puissiez écrire un fichier sur le disque dur du serveur (pas le disque sur lequel l'application est lancée) car la copie ne lira que sur le serveur.
il y a un nouveau manuel psycopg2 contenant des exemples pour toutes les options.
l'option copie est la plus efficace. Puis le executemany. Puis l'exécution avec pyformat.
d'après mon expérience, executemany
n'est pas plus rapide que de lancer plusieurs inserts vous-même.,
la manière la plus rapide est de formater un seul INSERT
avec beaucoup de valeurs vous-même, peut-être à l'avenir executemany
s'améliorera, mais pour l'instant il est assez lent
i sous-classe A list
et surcharger la méthode d'ajout, de sorte que lorsque a la liste atteint une certaine taille, je formate L'insertion pour l'exécuter
, Vous pourriez utiliser une nouvelle upsert bibliothèque :
$ pip install upsert
(vous pourriez avoir à pip install decorator
d'abord)
conn = psycopg2.connect('dbname=mydatabase')
cur = conn.cursor()
upsert = Upsert(cur, 'mytable')
for (selector, setter) in myrecords:
upsert.row(selector, setter)
Où selector
est un dict
objet comme {'name': 'Chris Smith'}
et setter
est un dict
comme { 'age': 28, 'state': 'WI' }
c'est presque aussi vite que d'écrire le code personnalisé[/UPDATE] et de l'exécuter directement avec psycopg2
... et il ça n'explosera pas si la rangée existe déjà.
le premier et le second seraient utilisés ensemble, et non séparément. Le troisième serait le plus efficace du point de vue du serveur, puisque le serveur ferait tout le travail dur.
après quelques tests, unnest semble souvent être une option extrêmement rapide, comme j'ai appris de @Clodoaldo Neto 's réponse à une question similaire.
data = [(1, 100), (2, 200), ...] # list of tuples
cur.execute("""CREATE TABLE table1 AS
SELECT u.id, u.var1
FROM unnest(%s) u(id INT, var1 INT)""", (data,))
cependant, il peut être délicat avec des données extrêmement grandes .
N'importe qui utilisant SQLalchemy pourrait essayer la version 1.2 qui a ajouté le soutien de insertion en vrac pour utiliser psycopg2.supplémentaire.execute_batch () au lieu de executemany lorsque vous initialisez votre moteur avec use_batch_mode=True like:
engine = create_engine(
"postgresql+psycopg2://scott:tiger@host/dbname",
use_batch_mode=True)
http://docs.sqlalchemy.org/en/latest/changelog/migration_12.html#change-4109
alors quelqu'un devrait utiliser SQLalchmey ne sera pas la peine d'essayer différentes combinaisons de sqla et psycopg2 et direct SQL together.
une question très liée: vrac insert avec SQLAlchemy ORM
toutes les routes mènent à Rome , mais certains d'entre eux traverse les montagnes, nécessite des ferries, mais si vous voulez y arriver rapidement, il suffit de prendre l'autoroute.
dans ce cas, l'autoroute doit utiliser le execute_batch () caractéristique de psycopg2 . La documentation le dit le mieux:
la mise en œuvre actuelle de executemany()
n'est pas particulièrement performante (selon un euphémisme extrêmement charitable). Ces fonctions peuvent être utilisées pour accélérer l'exécution répétée d'un énoncé par rapport à un ensemble de paramètres. En réduisant le nombre de roundtrips de serveur la performance peut être des ordres de grandeur mieux que d'utiliser executemany()
.
dans mon propre test execute_batch()
est environ deux fois plus rapide que executemany()
, et donne l'option de configurer le page_size pour de nouveaux réglages (si vous voulez presser les derniers 2-3% de performance du pilote).
la même fonctionnalité peut facilement être activée si vous utilisez SQLAlchemy en paramétrant use_batch_mode=True
comme paramètre lorsque vous instanciez le moteur avec create_engine()