efficace de la mémoire intégrée SqlAlchemy itérateur/générateur?
J'ai un ~10M enregistrement de la table MySQL que je l'interface avec l'aide de SqlAlchemy. J'ai trouvé que les requêtes sur de grands sous-ensembles de ce tableau permettra de consommer trop de mémoire, même si je pensais que j'étais l'aide d'un générateur de façon intelligente extraites petits morceaux de la base de données:
for thing in session.query(Things):
analyze(thing)
Pour éviter cela, je trouve que je dois construire mon propre itérateur qui mord en morceaux:
lastThingID = None
while True:
things = query.filter(Thing.id < lastThingID).limit(querySize).all()
if not rows or len(rows) == 0:
break
for thing in things:
lastThingID = row.id
analyze(thing)
Est-ce normal ou est-il quelque chose qui m'échappe concernant SA built-dans les générateurs?
La réponse à cette question semble indiquer que la consommation de la mémoire n'est pas à prévoir.
- J'ai quelque chose de très similaire, sauf qu'il donne de "chose". Fonctionne mieux que toutes les autres solutions
- N'est-il pas Chose.id > lastThingID? Et qu'est-ce que les "lignes"?
Vous devez vous connecter pour publier un commentaire.
Plus DBAPI implémentations entièrement lignes de tampon comme elles sont lues - ainsi, habituellement, avant la SQLAlchemy ORM obtient même une attente d'un résultat, la totalité des résultats est en mémoire.
Mais alors, la voie de Requête fonctionne, c'est qu'il est entièrement chargée, le résultat fourni par défaut avant de revenir à vous de vos objets. Le raisonnement ce qui concerne les requêtes qui sont plus que de simples instructions SELECT - se joint à d'autres tables qui peut retourner la même identité de l'objet de multiples fois dans un jeu de résultats (commun avec impatient de chargement), l'ensemble des lignes doit être dans la mémoire de sorte que les bons résultats peuvent être retournés autrement les collections et tel pourrait être que partiellement rempli.
Afin de Requête dispose d'une option pour modifier ce comportement, qui est le yield_per() appel http://www.sqlalchemy.org/docs/orm/query.html?highlight=yield_per#sqlalchemy.orm.query.Query.yield_per . Cet appel va provoquer la Requête de rendement des lignes de lots, où vous le donner la taille du lot. Comme les docs de l'état, ce n'est approprié que si vous ne faites tout type de chargement impatient de collections - c'est donc, fondamentalement, si vous savez vraiment ce que vous faites. Et aussi, si le sous-jacent DBAPI pré-tampons lignes , il y aura encore que la surcharge de la mémoire si l'approche ne échelles légèrement mieux que ne l'utilisez pas.
Je n'ai pratiquement jamais utiliser yield_per() - au lieu de cela, j'utilise une meilleure version de la LIMITE d'approche vous suggérons ci-dessus en utilisant les fonctions de la fenêtre. LIMIT et OFFSET avez un énorme problème que les très grandes valeurs de DÉCALAGE cause de la requête pour obtenir de plus en plus lentement, comme un DÉCALAGE de N causes à la page grâce à N lignes - c'est comme faire la même requête cinquante fois au lieu d'une, à chaque fois que la lecture d'un plus grand et plus grand nombre de lignes. Avec une fenêtre-l'approche par la fonction, je l'ai pré-extraction d'un ensemble de "fenêtre" valeurs qui font référence à des segments de la table, je veux sélectionner. Je puis émettre SELECT de déclarations que chaque traction de l'une de ces fenêtres à la fois.
La fenêtre de la fonction de l'approche est sur le wiki à http://www.sqlalchemy.org/trac/wiki/UsageRecipes/WindowedRangeQuery et je l'utilise avec beaucoup de succès.
Également de noter que pas toutes les bases de données de soutenir les fonctions de la fenêtre - vous besoin PG, Oracle ou SQL Server. À mon humble avis à l'aide d'au moins Postgresql est certainement la peine - si vous utilisez une base de données relationnelle, vous pourriez aussi bien utiliser le meilleur.
J'ai été regarder dans efficace traversée/recherche avec SQLAlchemy et souhaitez mettre à jour cette réponse.
Je pense que vous pouvez utiliser la tranche d'appel de bien limiter la portée d'une requête et vous pourriez réutiliser efficacement il.
Exemple:
.all()
est nécessaire. Je remarque la vitesse est beaucoup amélioré après le 1er appel..all()
les choses variable est une requête qui ne prend pas en charge len()Je ne suis pas un expert base de données, mais lors de l'utilisation de SQLAlchemy comme un simple Python couche d'abstraction (c'est à dire, ne pas utiliser l'ORM objet de Requête) je suis venu avec une solution de satisfaction à la requête de 300 m à la ligne de la table sans faire exploser l'utilisation de la mémoire...
Ici est un mannequin exemple:
Ensuite, j'utilise la SQLAlchemy
fetchmany()
méthode d'itération sur les résultats dans unwhile
boucle:Cette méthode m'a permis de faire toute sorte de l'agrégation de données sans aucune dangereux surcharge de la mémoire.
NOTE
lastream_results
fonctionne avec Postgres et lapyscopg2
adaptateur, mais je suppose que ça ne marchera pas avec tout DBAPI, ni avec n'importe quel pilote de base de données...Il y a un intéressant cas d'utilisation dans ce post de blog qui a inspiré ma méthode ci-dessus.
pymysql
), ce qui devrait être la accepté de répondre à mon humble avis.Dans l'esprit de Joel réponse, j'utilise la suite:
Autant que je sache, la première variante obtient encore de tous les tuples de la table (avec une requête SQL) mais qui construit l'ORM de présentation pour chaque entité lors de l'itération. Il est donc plus efficace que la construction d'une liste de toutes les entités avant l'itération, mais vous avez encore pour récupérer toutes les (premières) les données en mémoire.
Ainsi, à l'aide de LIMITE sur d'immenses tables sonne comme une bonne idée pour moi.
Utiliser LIMIT/OFFSET est mauvais, parce que vous devez trouver tous {OFFSET} colonnes avant, de sorte que le plus grand est le DÉCALAGE le plus de demande que vous obtenez.
À l'aide fenêtre de requête pour moi aussi, donne de mauvais résultats sur une grande table avec une grande quantité de données (vous attendre d'abord les résultats pour trop longtemps, qu'il n'est pas bon dans mon cas, pour les fragments de réponse web).
Meilleure approche présentée ici https://stackoverflow.com/a/27169302/450103. Dans mon cas, j'ai résolu le problème tout simplement en utilisant l'index sur le champ de date /heure et de l'extraction de la prochaine requête avec datetime>=previous_datetime. Stupide, parce que j'ai utilisé cet indice dans les différents cas avant, mais la pensée que pour la récupération de toutes les données de la fenêtre de requête serait mieux. Dans mon cas, j'ai eu tort.