web-dev-qa-db-fra.com

Psycopg2, Postgresql, Python: le moyen le plus rapide d'insérer en bloc

Je cherche le moyen le plus efficace d'insérer en masse des millions de nuplets dans une base de données. J'utilise Python, PostgreSQL et psycopg2 .

J'ai créé une longue liste de tulpes qui devraient être insérés dans la base de données, parfois avec des modificateurs tels que géométrique Simplify.

La façon naïve de le faire serait de formater une liste d'instructions INSERT, mais il y a trois autres méthodes que j'ai déjà lues:

  1. Utilisation de pyformat style pour une insertion paramétrique
  2. Utiliser executemany sur la liste des n-uplets, et 
  3. Utilisation de l'écriture des résultats dans un fichier et de COPY.

Il semble que le premier moyen soit le plus efficace, mais j'apprécierais vos idées et vos extraits de code me disant comment bien faire les choses.

38
Adam Matan

Oui, je voterais pour COPY, à condition que vous puissiez écrire un fichier sur le disque dur de server (et non sur celui sur lequel l'application est exécutée) car COPY lira uniquement le serveur.

14
Andy Shellam

Il existe un nouveau psycopg2 manual contenant des exemples pour toutes les options.

L'option COPY est la plus efficace. Puis l'exécutemany. Puis exécuter avec pyformat.

10
piro

d'après mon expérience, executemany n'est pas plus rapide que d'exécuter vous-même de nombreuses insertions, est le moyen le plus rapide de formater une seule INSERT avec beaucoup de valeurs vous-même, peut-être que dans l'avenir, executemany s'améliorera, mais pour l'instant, il est assez lent

je sous-classe un list et surcharger la méthode append, donc quand a la liste atteint une certaine taille je formate l'INSERT pour l'exécuter

7
FlashDD

Vous pouvez utiliser une nouvelle bibliothèque upsert :

$ pip install upsert

(vous devrez peut-être pip install decorator en premier)

conn = psycopg2.connect('dbname=mydatabase')
cur = conn.cursor()
upsert = Upsert(cur, 'mytable')
for (selector, setter) in myrecords:
    upsert.row(selector, setter)

selector est un objet dict comme {'name': 'Chris Smith'} et setter est un dict comme { 'age': 28, 'state': 'WI' }

C'est presque aussi rapide que d'écrire du code INSERT [/ UPDATE] personnalisé et de l'exécuter directement avec psycopg2... et que cela n'explosera pas si la ligne existe déjà.

6
Seamus Abshere

Toute personne utilisant SQLalchemy pourrait essayer la version 1.2 qui ajoutait la prise en charge de l'insertion en bloc pour utiliser psycopg2.extras.execute_batch () à la place de executemany lorsque vous initialisez votre moteur avec use_batch_mode = True comme:

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

Ensuite, quelqu'un devra utiliser SQLalchmey pour ne pas essayer différentes combinaisons de sqla et psycopg2 et diriger SQL ensemble.

1
user2189731

Le premier et le second seraient utilisés ensemble, pas séparément. Le troisième serait le plus efficace en termes de serveur, puisque le serveur ferait all le travail acharné.

1

Après quelques tests, unnest semble souvent être une option extrêmement rapide, comme je l’ai appris de @Clodoaldo Neto 's answer à 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 volumineuses .

1
n1000

Une question très liée: Insertion en bloc avec SQLAlchemy ORM


_ {Tous les chemins mènent à Rome}, mais certains 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 le execute_batch () feature de psycopg2 . La documentation dit le meilleur:

L’actuelle implémentation de executemany() (avec 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 sur les serveurs, les performances peuvent être meilleures que par le biais de executemany().

Dans mon propre test, execute_batch() est environ deux fois plus rapide que executemany(), et donne la possibilité de configurer la taille de la page pour une optimisation supplémentaire (si vous souhaitez extraire les derniers 2 ou 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 en tant que paramètre lorsque vous instanciez le moteur avec create_engine().

0
chjortlund