Pandas: Utilisez des insertions multi-lignes pour des accélérations massives sur to_sql sur des connexions à latence élevée

Créé le 1 déc. 2014  ·  48Commentaires  ·  Source: pandas-dev/pandas

J'ai essayé d'insérer ~ 30k lignes dans une base de données mysql en utilisant pandas-0.15.1, oursql-0.9.3.1 et sqlalchemy-0.9.4. Parce que la machine est comme de l'autre côté de l'Atlantique, appeler data.to_sql prenait plus d'une heure pour insérer les données. Lors de l'inspection avec wireshark, le problème est qu'il envoie un insert pour chaque ligne, puis attend l'ACK avant d'envoyer le suivant, et, pour faire court, les temps de ping me tuent.

Cependant, en suivant les instructions de SQLAlchemy , j'ai changé

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)

pour

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))

et l'ensemble de l'opération se termine en moins d'une minute. (Pour vous épargner un clic, la différence est entre plusieurs appels à insert into foo (columns) values (rowX) et un énorme insert into foo (columns) VALUES (row1), (row2), row3) ). Étant donné la fréquence à laquelle les gens sont susceptibles d'utiliser des pandas pour insérer de gros volumes de données, cela ressemble à une énorme victoire qu'il serait formidable d'inclure plus largement.

Quelques défis :

  • Toutes les bases de données ne prennent pas en charge les insertions multilignes (SQLite et SQLServer ne le faisaient pas dans le passé, bien qu'ils le fassent maintenant). Je ne sais pas comment vérifier cela via SQLAlchemy
  • Le serveur MySQL que j'utilisais ne me permettait pas d'insérer toutes les données en une seule fois, je devais définir la taille du morceau (5k fonctionnait bien, mais je suppose que les 30k complets étaient trop). Si nous en faisions l'insertion par défaut, la plupart des gens devraient ajouter une taille de bloc (qui pourrait être difficile à calculer, car elle pourrait être déterminée par la taille de paquet maximale du serveur).

La façon la plus simple de le faire serait d'ajouter un paramètre booléen multirow= (par défaut False ) à la fonction to_sql , puis de laisser l'utilisateur responsable de la définition de la taille de bloc, mais peut-être y a-t-il un meilleur moyen?

Les pensées?

IO SQL Performance

Commentaire le plus utile

Nous avons compris comment faire un patch de singe - cela pourrait être utile à quelqu'un d'autre. Ayez ce code avant d'importer des pandas.

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

Tous les 48 commentaires

Cela semble raisonnable. Merci d'avoir enquêté là-dessus !

Pour l'implémentation, cela dépendra de la façon dont sqlalchemy traite les saveurs de base de données qui ne le supportent pas (je ne peux pas tester cela pour le moment, mais il semble que sqlalchemy génère une erreur (par exemple http://stackoverflow.com/questions/ 23886764/multiple-insert-statements-in-mssql-with-sqlalchemy). De plus, si cela a pour conséquence que beaucoup de gens devront définir chunksize, ce n'est en effet pas une bonne idée de le faire par défaut (sauf si nous définissons chunksize à une valeur par défaut).
Donc, ajouter un mot-clé semble peut-être mieux.

@artemyk @mangecoeur @hayd @danielballan

Apparemment, SQLAlchemy a un indicateur dialect.supports_multivalues_insert (voir par exemple http://pydoc.net/Python/SQLAlchemy/0.8.3/sqlalchemy.sql.compiler/ , éventuellement appelé supports_multirow_insert dans d'autres versions, https ://www.mail-archive.com/[email protected]/msg202880.html ).

Étant donné que cela a le potentiel d'accélérer considérablement les insertions et que nous pouvons vérifier facilement le support, je pense que nous pourrions peut-être le faire par défaut et également définir la taille de morceau sur une valeur par défaut (par exemple, des morceaux de 16 ko ... je ne sais pas ce qui est trop grand dans la plupart des situations). Si l'insertion multiligne échoue, nous pourrions lancer une exception suggérant de réduire la taille de bloc ?

Maintenant, j'ai juste besoin de persuader les gens de SQLAlchemy de définir supports_multivalues_insert sur true sur SQL Server> 2005 (je l'ai piraté dans le code et cela fonctionne bien, mais ce n'est pas activé par défaut).

Sur une note plus pertinente, je pense que la taille des morceaux pourrait être délicate. Sur ma configuration mysql (que j'ai probablement configurée pour autoriser les gros paquets), je peux définir chunksize=5000, sur ma configuration SQLServer, 500 était trop grand, mais 100 a bien fonctionné. Cependant, il est probablement vrai que la plupart des avantages de cette technique proviennent du passage de l'insertion d'une ligne à la fois à 100, plutôt que de 100 à 1000.

Et si chunksize=None signifiait "Choisir de manière adaptative une taille de bloc" ? Essayez quelque chose comme 5000, 500, 50, 1. Les utilisateurs peuvent désactiver cette option en spécifiant une taille de bloc. Si la surcharge de ces tentatives est trop importante, j'aime la suggestion de @maxgrenderjones : chunksize=10 est une meilleure valeur par défaut que chunksize=1 .

Sur ce dernier commentaire " chunksize=10 est une meilleure valeur par défaut que chunksize=1 " -> ce n'est pas tout à fait vrai, je pense. La situation actuelle est de faire _une_ instruction d'exécution qui se compose d'instructions d'insertion multilignes sur une seule ligne (qui n'est pas une taille de bloc de 1), tandis que chunksize=10 signifierait faire beaucoup d'instructions d'exécution avec à chaque fois une multi-ligne insérer.
Et je ne sais pas si c'est nécessairement plus rapide, mais cela dépend beaucoup de la situation. Par exemple avec le code actuel et avec une base de données sqlite locale :

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

Mais bien sûr, cela n'utilise pas la fonction multi-lignes

Nous avons compris comment faire un patch de singe - cela pourrait être utile à quelqu'un d'autre. Ayez ce code avant d'importer des pandas.

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

Peut-être pouvons-nous commencer par ajouter cette fonctionnalité via un nouveau mot-clé multirow=True (avec une valeur par défaut de False pour l'instant), puis nous pourrons toujours voir plus tard si nous pouvons l'activer par défaut ?

@maxgrenderjones @nhockham intéressé à faire un PR pour ajouter ceci ?

@jorisvandenbossche Je pense qu'il est risqué de commencer à ajouter des arguments de mots clés pour traiter des profils de performances spécifiques. Si vous pouvez garantir qu'il est plus rapide dans tous les cas (si nécessaire en lui faisant déterminer la meilleure méthode en fonction des entrées), vous n'avez pas du tout besoin d'un indicateur.

Différentes configurations de base de données peuvent avoir différentes optimisations de performances (différents profils de performances de base de données, local vs réseau, grande mémoire vs SSD rapide, etc.), si vous commencez à ajouter des drapeaux de mots clés pour chacun, cela devient un gâchis.

Je suggérerais de créer des sous-classes de SQLDatabase et SQLTable pour traiter des implémentations spécifiques aux performances, elles seraient utilisées via l'API orientée objet. Peut-être qu'une méthode de "commutation de backend" pourrait être ajoutée, mais franchement, l'utilisation de l'API OO est très simple, donc c'est probablement exagéré pour ce qui est déjà un cas d'utilisation spécialisé.

J'ai créé une telle sous-classe pour charger de grands ensembles de données sur Postgres (il est en fait beaucoup plus rapide d'enregistrer des données au format CSV puis d'utiliser les commandes COPY FROM sql non standard intégrées que d'utiliser des insertions, voir https://gist.github. com/mangecoeur/1fbd63d4758c2ba0c470#file-pandas_postgres-py). Pour l'utiliser, il vous suffit de faire PgSQLDatabase(engine, <args>).to_sql(frame, name,<kwargs>)

Juste pour référence, j'ai essayé d'exécuter le code par @jorisvandenbossche (poste du 3 décembre) en utilisant la fonction multiligne. C'est un peu plus lent. Donc, les compromis de vitesse ici ne sont pas triviaux :

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

De plus, je reconnais que l'ajout de paramètres de mots clés est risqué. Cependant, la fonctionnalité multiligne semble assez fondamentale. De plus, "monkey-patching" n'est probablement pas plus robuste aux changements d'API que les paramètres de mots clés.

C'est comme je le soupçonnais. Le correctif de singe n'est pas la solution que je suggérais - plutôt que nous expédions un certain nombre de sous-classes axées sur les performances que l'utilisateur informé pourrait utiliser via l'interface OO (pour éviter de charger l'API fonctionnelle avec trop d'options)

-----Message d'origine-----
De : "Artemy Kolchinsky" [email protected]
Envoyé: ‎26/‎02/‎2015 17:13
À : "pydata/pandas" [email protected]
Cc : "mangecoeur" jon. [email protected]
Objet : Re : [pandas] Utilisez des insertions multi-lignes pour des accélérations massives sur les connexions à latence élevée to_sqlover (#8953)

Juste pour référence, j'ai essayé d'exécuter le code par @jorisvandenbossche (poste du 3 décembre) en utilisant la fonction multiligne. C'est un peu plus lent. Donc, les compromis de vitesse ici ne sont pas triviaux :
Dans [4] : engine = create_engine('sqlite:///:memory:') #, echo='debug')

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

Dans [6] :

Dans [6] : %timeit df.to_sql('test_default', moteur, if_exists='replace')
1 boucles, au mieux des 3 : 1,05 s par boucle

Dans [7] :

Dans [7] : depuis pandas.io.sql, importez SQLTable

Dans [8] :

Dans [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))
... :

Dans [9] : SQLTable._execute_insert = _execute_insert

Dans [10] :

Dans [10] : recharger(pd)
Sortie[10] :

Dans [11] :

Dans [11] : %timeit df.to_sql('test_default', moteur, if_exists='replace', chunksize=10)
1 boucles, au mieux des 3 : 9,9 s par boucle
De plus, je reconnais que l'ajout de paramètres de mots clés est risqué. Cependant, la fonctionnalité multiligne semble assez fondamentale. De plus, "monkey-patching" n'est probablement pas plus robuste aux changements d'API que les paramètres de mots clés.

Répondez directement à cet e-mail ou consultez-le sur GitHub.

Selon le titre initial du ticket, je ne pense pas que cette approche soit préférable dans tous les cas, donc je n'en ferais pas la valeur par défaut. Cependant, sans cela, les pandas to_sql sont inutilisables pour moi, il est donc suffisamment important pour moi de continuer à demander le changement. (C'est aussi devenu la première chose que je change lorsque je mets à jour ma version de pandas). En ce qui concerne les valeurs chunksize sensibles, je ne pense pas qu'il y ait un vrai n , car la taille du paquet dépendra du nombre de colonnes (et de ce qu'elles contiennent) de manière difficile à prévoir. . Malheureusement, SQLServer échoue avec un message d'erreur qui semble totalement indépendant (mais ne l'est pas) si vous définissez le chunksize trop élevé (ce qui explique probablement pourquoi les insertions multilignes ne sont pas activées, sauf avec un correctif dans SQLAlchemy), mais cela fonctionne bien avec mysql . Les utilisateurs peuvent avoir besoin d'expérimenter pour déterminer quelle valeur de n est susceptible d'entraîner une taille de paquet acceptable (quelle que soit leur base de données de sauvegarde). Le fait que les pandas aient choisi n est susceptible de nous amener bien plus loin dans les détails de l'implémentation que nous ne le souhaitons (c'est-à-dire la direction opposée à l'approche SQLALchemy d'abstraction maximale possible)

En bref, ma recommandation serait de l'ajouter en tant que mot-clé, avec quelques commentaires utiles sur la façon de l'utiliser. Ce ne serait pas la première fois qu'un mot-clé était utilisé pour sélectionner une implémentation (voir : http://pandas.pydata.org/pandas-docs/dev/generated/pandas.DataFrame.apply.html) mais ce n'est peut-être pas le cas. t le meilleur exemple, car je n'ai pas la première idée de ce que signifie raw= , même après avoir lu l'explication !

J'ai remarqué qu'il consomme également énormément de mémoire. Comme un DataFrame de plus de 1,6 Go avec quelque 700 000 lignes et 301 colonnes nécessite près de 34 Go lors de l'insertion ! C'est comme trop inefficace. Des idées sur pourquoi cela pourrait être le cas? Voici un extrait d'écran :

image

Salut les gars,
des avancées sur ce sujet ?

J'essaie d'insérer environ 200 000 lignes à l'aide de to_sql, mais cela prend une éternité et consomme énormément de mémoire ! L'utilisation de chuncksize aide avec la mémoire mais la vitesse est toujours très lente.

Mon impression, en regardant la trace MSSQL DBase, est que l'insertion est en fait effectuée une ligne à la fois.

La seule approche viable consiste maintenant à vider dans un fichier csv sur un dossier partagé et à utiliser BULK INSERT. Mais c'est très ennuyeux et inélégant!

@andreacassioli Vous pouvez utiliser odo pour insérer un DataFrame dans une base de données SQL via un fichier CSV intermédiaire. Voir Chargement de fichiers CSV dans des bases de données SQL .

Je ne pense pas que vous puissiez vous approcher des performances de BULK INSERT en utilisant ODBC.

@ostrokach merci, en effet j'utilise maintenant des fichiers csv. Si je pouvais m'en approcher, j'échangerais un peu de temps contre de la simplicité !

J'ai pensé que cela pourrait aider quelqu'un:
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 n'utilise pas l'ORM, seulement sqlalchemy Core (ce que l'entrée de doc suggère d'utiliser pour les gros inserts)

y a-t-il un consensus sur la façon de contourner ce problème en attendant ? J'insère plusieurs millions de lignes dans postgres et cela prend une éternité. Est-ce que CSV / odo est la voie à suivre ?

@russlamb un moyen pratique de résoudre ce problème consiste simplement à télécharger en masse. C'est quelqu'un de spécifique à la base de données, donc odo a des solutions pour postgresl (et peut-être mysql ) je pense. pour quelque chose comme sqlserver, vous devez "le faire vous-même" (IOW vous devez l'écrire).

Pour sqlserver, j'ai utilisé le pilote FreeTDS (http://www.freetds.org/software.html et https://github.com/mkleehammer/pyodbc ) avec des entités SQLAlchemy qui ont entraîné des insertions très rapides (20K lignes par trame de données) :

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)

Est-ce que CSV / odo est la voie à suivre ?

Cette solution sera presque toujours plus rapide, je pense, quels que soient les paramètres multi-lignes / chunksize.

Mais, @russlamb , il est toujours intéressant de savoir si un tel mot clé à plusieurs lignes serait une amélioration dans votre cas. Voir par exemple https://github.com/pandas-dev/pandas/issues/8953#issuecomment -76139975 sur un moyen de tester facilement cela.

Je pense qu'il y a un accord sur le fait que nous voulons avoir un moyen de spécifier cela (sans nécessairement changer la valeur par défaut). Donc, si quelqu'un veut faire un PR pour cela, c'est certainement le bienvenu.
Il n'y a eu que quelques discussions sur la façon d'ajouter cette capacité (nouveau mot-clé vs sous-classe utilisant l'API OO).

@jorisvandenbossche Le document que j'ai lié ci-dessus mentionne "Alternativement, l'ORM SQLAlchemy propose la suite de méthodes Bulk Operations, qui fournit des crochets dans les sous-sections du processus d'unité de travail afin d'émettre des constructions INSERT et UPDATE au niveau du noyau avec un petit degré d'ORM - automatisation basée sur ".

Ce que je suggère est d'implémenter une version spécifique de sqlserver pour to_sql qui sous le capot utilise les ORM SQLAlchemy pour les accélérations comme dans le code que j'ai posté ci-dessus.

Cela a été proposé auparavant. La façon dont vous allez est d'implémenter un sql pandas
classe optimisée pour un backend. J'ai posté un essentiel dans le passé pour l'utilisation
postgres COPY FROM qui est beaucoup plus rapide. Cependant quelque chose de similaire
est maintenant disponible dans odo, et construit de manière plus robuste. Il n'y a pas grand chose
point IMHO dans la duplication du travail d'odo.

Le 7 mars 2017 à 00h53, "Andrei Sura" [email protected] a écrit :

@jorisvandenbossche https://github.com/jorisvandenbossche Le document
J'ai lié les mentions ci-dessus "Alternativement, l'ORM SQLAlchemy propose le Bulk
Suite d'opérations de méthodes, qui fournissent des crochets dans les sous-sections de la
processus d'unité de travail afin d'émettre INSERT et UPDATE au niveau du noyau
construit avec un petit degré d'automatisation basée sur ORM."

Ce que je suggère, c'est d'implémenter une version spécifique de sqlserver pour
"to_sql" qui sous le capot utilise le noyau SQLAlchemy pour les accélérations.


Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/pandas-dev/pandas/issues/8953#issuecomment-284437587 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AAtYVDXKLuTlsh9ycpMQvU5C0hs_RxuYks5rjCwBgaJpZM4DCjLh
.

Vous avez également remarqué que sqlalchemy pourrait être noyau à la place. A moins que quelque chose
a beaucoup changé, seul le noyau sqlalchemy est utilisé dans tous les cas, pas d'orm. Si vous
voulez accélérer plus que d'utiliser le noyau, vous devez passer au niveau inférieur, db
optimisation spécifique

Le 7 mars 2017 à 00h53, "Andrei Sura" [email protected] a écrit :

@jorisvandenbossche https://github.com/jorisvandenbossche Le document
J'ai lié les mentions ci-dessus "Alternativement, l'ORM SQLAlchemy propose le Bulk
Suite d'opérations de méthodes, qui fournissent des crochets dans les sous-sections de la
processus d'unité de travail afin d'émettre INSERT et UPDATE au niveau du noyau
construit avec un petit degré d'automatisation basée sur ORM."

Ce que je suggère, c'est d'implémenter une version spécifique de sqlserver pour
"to_sql" qui sous le capot utilise le noyau SQLAlchemy pour les accélérations.


Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/pandas-dev/pandas/issues/8953#issuecomment-284437587 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AAtYVDXKLuTlsh9ycpMQvU5C0hs_RxuYks5rjCwBgaJpZM4DCjLh
.

Est-ce que ça se règle/s'occupe de ça ? À partir de maintenant, l'insertion de trames de données pandas dans une base de données SQL est extrêmement lente, sauf s'il s'agit d'une trame de données jouet. Décidons d'une solution et faisons-la avancer ?

@dfernan Comme mentionné ci-dessus, vous voudrez peut-être regarder le fichier odo . L'utilisation d'un fichier CSV intermédiaire sera toujours d'un ordre de grandeur plus rapide que de passer par sqlalchemy, quel que soit le type d'améliorations qui se produisent ici...

@ostrokach , je ne suis pas convaincu que le comportement d'odo corresponde à ce que souhaite l'utilisateur typique de Pandas. Les insertions multi-lignes sur ODBC sont probablement assez rapides pour la plupart des analystes.

Pour parler pour moi, je viens de passer quelques heures à passer du patch de singe ci-dessus à odo. La durée d'exécution des pandas simples était de plus de 10 heures, RBAR. Le patch de singe s'exécute en 2 sur le même jeu de données.
La route odo/CSV était plus rapide, comme prévu, mais pas suffisamment pour que cela en vaille la peine. J'ai tripoté des problèmes de conversion CSV dont je ne me souciais pas beaucoup, tout cela au nom d'éviter le patch de singe. J'importe 250 000 lignes à partir d'environ 10 bases de données mysql et PG dans une zone commune de Postgres, pour l'analyse NLP.

Je connais intimement les approches de chargement en masse adoptées par odo. Je les utilise depuis des années où je commence avec des données CSV. Il y a des limitations clés pour eux:

  1. Pour le cas df->CSV->Postgres, un accès shell et une étape scp sont nécessaires pour obtenir le CSV sur l'hôte PG. Il semble que @mangecoeur ait contourné cela avec un flux vers STDIN.
  2. Pour mon objectif (250 000 lignes de commentaires, avec de nombreux cas particuliers dans le contenu du texte), j'ai eu du mal à obtenir les bons paramètres CSV. Je ne voulais pas suffisamment de gains de performance pour continuer à investir là-dedans.

Je reviens au patch pour pouvoir passer au travail d'analyse.

Je suis d'accord avec @jorisvandenbossche , @maxgrenderjones. Une option (pas une valeur par défaut) pour choisir cela serait extrêmement utile. Le point de @artemyk à propos de dialect.supports_multivalues_insert pourrait même en faire une valeur par défaut raisonnable.

Je suis heureux de soumettre un PR si cela pouvait faire avancer les choses.

juste pour ajouter mon expérience avec odo, cela n'a pas fonctionné pour les insertions en vrac MS Sql en raison d'un problème connu avec l'encodage. L'insert imho m-row est une bonne solution pratique pour la plupart des personnes.

@markschwarz une option pour permettre à cela de fonctionner plus rapidement serait la bienvenue !

En traçant les requêtes à l'aide de sqlite, il me semble que j'obtiens des multi-inserts lors de l'utilisation chunksize :

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'))

(sans le patch de singe, c'est-à-dire)

Fait intéressant, avec le patch de singe, il se casse lorsque je lui donne une taille de bloc de 10 ^ 5, mais pas de 10 ^ 3. L'erreur est "trop ​​de variables sql" sur sqlite.

@makmanalp , je n'ai pas encore tracé sur PG pour vérifier ce comportement, mais j'ai presque toujours défini chunksize sur insert. Dans mon exemple ci-dessus, je l'ai défini au hasard sur 5 valeurs entre 200 et 5000. Je n'ai pas vu de différences de temps écoulées drastiques entre ces choix, sans le patch de singe. Avec le patch, le temps écoulé a chuté d'environ 80 %.

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

Ce patch de singe fonctionne-t-il toujours ? Je l'ai essayé sur MS SQL Server mais je n'ai pas vu d'amélioration. De plus, il lance une exception :

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

@hangyao Je pense que ce correctif est spécifique à l'implémentation, l'une de ces choses que python DBAPI laisse au pilote DBAPI sur la façon de gérer. Donc ça peut être plus rapide ou pas. RE : l'erreur de syntaxe, je n'en suis pas sûr.

Je pense à ajouter la fonction ci-dessous dans le fichier /io/sql.py à la ligne 507 dans la fonction _engine_builder dans un nouveau IF clouse à la ligne 521 faisant le 'nouveau' _engine_builder ci-dessous fragment. Je l'ai brièvement testé sur mon environnement et cela fonctionne très bien pour les bases de données MSSQL, atteignant des accélérations> 100x. Je ne l'ai pas encore testé sur d'autres bases de données.

La chose de m'empêcher de faire un PR est que je pense qu'il est plus difficile de le rendre propre et sûr que de simplement l'insérer comme ci-dessous, ce n'est peut-être pas toujours la spécification souhaitée et d'ajouter un commutateur booléen, qui active/désactive ce paramètre , (par exemple fast_executemany=True ) dans le to_sql semblait être un effort un peu trop important pour le faire sans demander, je pense.

Donc mes questions sont :

  • La fonction ci-dessous fonctionne-t-elle et augmente-t-elle également la vitesse d'INSERT pour PostgreSQL ?

  • L'événement pandas veut-il cet extrait dans sa source ? Si c'est le cas:

  • Souhaitez-vous ajouter cette fonction à la fonctionnalité par défaut sql.py ou existe-t-il un meilleur endroit pour l'ajouter ?

J'adore entendre certains commentaires.

Source pour la réponse : 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 Il y a eu récemment une fusion de relations publiques à ce sujet : https://github.com/pandas-dev/pandas/pull/19664. Je n'ai pas encore regardé en détail votre message, et ce n'est certainement pas exactement la même chose (il utilise l'attribut supports_multivalues_insert du moteur sqlalchemy), mais juste pour vous assurer que vous en êtes conscient au cas où cela aiderait déjà ainsi que.

Ce sont de bonnes nouvelles! Je n'ai pas regardé dans le PR mais je le comparerai ce week-end et je reviendrai avec les résultats. Merci pour l'information.

J'essayais juste 0.23.0 RC2 (sur postgresql) et au lieu d'avoir une amélioration des performances, mon script est devenu beaucoup plus lent. La requête DB est devenue beaucoup plus rapide, mais en mesurant le temps pour to_sql() , elle est devenue jusqu'à 1,5 fois plus lente (comme de 7 à 11 secondes)...

Je ne suis pas sûr que le ralentissement provienne de ce PR car je viens de tester le RC.

Quelqu'un d'autre a-t-il rencontré le même problème ?

@ schettino72 Combien de données avez-vous inséré ?

Environ 30 000 lignes avec 10 colonnes. Mais vraiment presque tout ce que j'essaie est plus lent (SQL est plus rapide mais globalement plus lent). Il crée une énorme instruction SQL où il y a une interpolation de valeur pour CHAQUE valeur. Quelque chose comme

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

J'ai trouvé d6tstack beaucoup plus simple à utiliser, c'est un d6tstack.utils.pd_to_psql(df, cfg_uri_psql, 'benchmark', if_exists='replace') ligne et c'est beaucoup plus rapide que df.to_sql() . Prend en charge postgres et mysql. Voir https://github.com/d6t/d6tstack/blob/master/examples-sql.ipynb

J'utilise la solution Monkey Patch :

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

depuis un certain temps maintenant, mais maintenant j'obtiens une erreur:

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

Est-ce que quelqu'un d'autre reçoit ça? Je suis sur Python 3.6.5 (Anaconda) et pandas==0.23.0

est-ce que ça se règle ? Actuellement, df.to_sql est extrêmement lent et ne peut pas du tout être utilisé dans de nombreux cas d'utilisation pratiques. Le projet Odo semble déjà abandonné.
J'ai les cas d'utilisation suivants dans les séries chronologiques financières où df.to_sql n'est pratiquement pas utilisable :
1) copie des données csv historiques dans la base de données postgres - impossible d'utiliser df.to_sql et a dû utiliser un code personnalisé autour de la fonctionnalité psycopg2 copy_from
2) les données en continu (venant dans un lot d'environ 500 à 3000 lignes par seconde) doivent être déversées dans la base de données postgres - encore une fois, les performances de df.to_sql sont assez décevantes car il faut trop de temps pour insérer ces lots naturels de données dans postgres.
Le seul endroit où je trouve df.to_sql utile maintenant est de créer des tables automatiquement !!! - ce qui n'est pas le cas d'utilisation pour lequel il a été conçu.
Je ne sais pas si d'autres personnes partagent également la même préoccupation, mais ce problème nécessite une certaine attention pour que les interfaces "dataframes-to-database" fonctionnent correctement.
Avoir hâte.

Hé, j'obtiens cette erreur lorsque j'essaie d'effectuer une multi-insertion dans une base de données SQLite :

C'est mon code:
df.to_sql("financial_data", con=conn, if_exists="append", index=False, method="multi")

et j'obtiens cette erreur :

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)

Pourquoi cela arrive-t-il? J'utilise Python 3.7.3 (Anaconda), pandas 0.24.2 et sqlite3 2.6.0.

Merci beaucoup d'avance!

@jconstanzo pouvez-vous l'ouvrir comme un nouveau numéro ?
Et si possible, pouvez-vous essayer de fournir un exemple reproductible ? (par exemple, un petit exemple de dataframe qui peut montrer le problème)

@jconstanzo Ayant le même problème ici. L'utilisation method='multi' (dans mon cas, en combinaison avec chunksize ) semble déclencher cette erreur lorsque vous essayez d'insérer dans une base de données SQLite.

Malheureusement, je ne peux pas vraiment fournir d'exemple de dataframe car mon jeu de données est énorme, c'est la raison pour laquelle j'utilise method et chunksize en premier lieu.

Je suis désolé pour le retard. Je viens d'ouvrir un sujet pour ce problème : https://github.com/pandas-dev/pandas/issues/29921

Cette page vous a été utile?
0 / 5 - 0 notes