109 votes

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.

132voto

zzzeek Points 22617

La plupart des 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 fonction de fenêtre 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.

17voto

Joel Points 129

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:

window_size = 10  # or whatever limit you like
window_idx = 0
while True:
    start,stop = window_size*window_idx, window_size*(window_idx+1)
    things = query.slice(start, stop).all()
    if things is None:
        break
    for thing in things:
        analyze(thing)
    if len(things) < window_size:
        break
    window_idx += 1

2voto

Pankrat Points 2145

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.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X