SQLAlchemy: création ou réutilisation d'une session

99

Juste une petite question: SQLAlchemy parle d' appeler sessionmaker()une fois, mais d'appeler la Session()classe résultante chaque fois que vous devez parler à votre base de données. Pour moi, cela signifie que la seconde que je ferais mon premier session.add(x)ou quelque chose de similaire, je ferais d'abord

from project import Session
session = Session()

Ce que j'ai fait jusqu'à présent, c'était de faire l'appel session = Session()dans mon modèle une fois et ensuite toujours importer la même session n'importe où dans mon application. Puisqu'il s'agit d'une application Web, cela signifierait généralement la même chose (lorsqu'une vue est exécutée).

Mais où est la différence? Quel est l'inconvénient d'utiliser une session tout le temps par rapport à son utilisation pour ma base de données jusqu'à ce que ma fonction soit terminée, puis en en créant une nouvelle la prochaine fois que je veux parler à ma base de données?

Je comprends que si j'utilise plusieurs threads, chacun devrait avoir sa propre session. Mais en utilisant scoped_session(), je m'assure déjà que ce problème n'existe pas, n'est-ce pas?

Veuillez préciser si l'une de mes hypothèses est erronée.

javex
la source

Réponses:

226

sessionmaker()est une usine, elle est là pour encourager le placement des options de configuration pour créer de nouveaux Sessionobjets en un seul endroit. Il est facultatif, en ce sens que vous pouvez tout aussi facilement appeler Session(bind=engine, expire_on_commit=False)chaque fois que vous avez besoin d'un nouveau Session, sauf que c'est verbeux et redondant, et je voulais arrêter la prolifération des «assistants» à petite échelle qui ont chacun abordé la question de cette redondance dans une nouvelle et de manière plus déroutante.

C'est donc sessionmaker()juste un outil pour vous aider à créer des Sessionobjets lorsque vous en avez besoin.

Partie suivante. Je pense que la question est de savoir quelle est la différence entre faire un nouveau Session()à divers moments et simplement en utiliser un tout au long. La réponse, pas grand-chose. Sessionest un conteneur pour tous les objets que vous y mettez, puis il garde également une trace d'une transaction ouverte. Au moment où vous appelez rollback()ou commit(), la transaction est terminée et le Sessionn'a pas de connexion à la base de données jusqu'à ce qu'il soit appelé à émettre à nouveau SQL. Les liens qu'il détient vers vos objets mappés sont des références faibles, à condition que les objets soient exempts de modifications en attente, de sorte que même à cet égard, il Sessionse videra dans un nouvel état lorsque votre application perdra toutes les références aux objets mappés. Si vous le laissez avec sa valeur par défaut"expire_on_commit", alors tous les objets ont expiré après une validation. Si cela Sessiondure cinq ou vingt minutes et que toutes sortes de choses ont changé dans la base de données la prochaine fois que vous l'utiliserez, elle chargera tout nouvel état la prochaine fois que vous accéderez à ces objets même s'ils sont restés en mémoire pendant vingt minutes.

Dans les applications Web, nous disons généralement, pourquoi ne pas créer une nouvelle marque Sessionà chaque demande, plutôt que d'utiliser la même chose encore et encore. Cette pratique garantit que la nouvelle demande commence «propre». Si certains objets de la requête précédente n'ont pas encore été récupérés, et si peut-être vous avez désactivé "expire_on_commit", peut-être qu'un état de la requête précédente est toujours en suspens, et cet état peut même être assez ancien. Si vous faites attention de laisser expire_on_commitallumé et d'appeler définitivement commit()ou rollback()à la fin de la demande, alors c'est bien, mais si vous commencez avec un tout nouveau Session, il n'y a même pas de question que vous commencez propre. Donc l'idée de commencer chaque demande avec un nouveauSessionest vraiment le moyen le plus simple de vous assurer que vous commencez à neuf et de rendre l'utilisation de expire_on_commitpresque facultative, car cet indicateur peut entraîner beaucoup de SQL supplémentaire pour une opération qui appelle commit()au milieu d'une série d'opérations. Je ne sais pas si cela répond à votre question.

Le prochain tour est ce que vous mentionnez à propos du filetage. Si votre application est multithread, nous vous recommandons de vous assurer que l' Sessionutilisation est locale à ... quelque chose. scoped_session()par défaut le rend local au thread actuel. Dans une application Web, le local à la demande est en fait encore meilleur. Flask-SQLAlchemy envoie en fait une "fonction d'étendue" personnalisée à scoped_session()afin que vous obteniez une session à portée de requête. L'application Pyramid moyenne colle la session dans le registre "request". Lorsque vous utilisez des schémas comme ceux-ci, l'idée «créer une nouvelle session sur demande de démarrage» continue de ressembler à la manière la plus simple de garder les choses en ordre.

zzzeek
la source
17
Wow, cela répond à toutes mes questions sur la partie SQLAlchemy et ajoute même quelques informations sur Flask et Pyramid! Bonus supplémentaire: les développeurs répondent;) J'aurais aimé pouvoir voter plus d'une fois. Merci beaucoup!
javex
Une clarification, si possible: vous dites expire_on_commit "peut entraîner beaucoup de SQL supplémentaire" ... pouvez-vous donner plus de détails? Je pensais que expire_on_commit concernait uniquement ce qui se passe dans la RAM, pas ce qui se passe dans la base de données.
Veky
3
expire_on_commit peut entraîner plus de SQL si vous réutilisez la même session à nouveau, et certains objets traînent toujours dans cette session, lorsque vous y accédez, vous obtiendrez un SELECT sur une seule ligne pour chacun d'eux lorsqu'ils s'actualisent chacun individuellement leur état par rapport à la nouvelle transaction.
zzzeek du
1
Salut, @zzzeek. Merci pour l'excellente réponse. Je suis très nouveau en python et je veux clarifier plusieurs choses: 1) Est-ce que je comprends bien quand je crée une nouvelle "session" en appelant la méthode Session (), cela créera une transaction SQL, puis la transaction sera ouverte jusqu'à ce que je valide / annule la session ? 2) Session () utilise-t-elle une sorte de pool de connexions ou établit-elle une nouvelle connexion à SQL à chaque fois?
Alex Gurskiy
27

En plus de l'excellente réponse de zzzeek, ​​voici une recette simple pour créer rapidement des sessions jetables et autonomes:

from contextlib import contextmanager

from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker

@contextmanager
def db_session(db_url):
    """ Creates a context with an open SQLAlchemy session.
    """
    engine = create_engine(db_url, convert_unicode=True)
    connection = engine.connect()
    db_session = scoped_session(sessionmaker(autocommit=False, autoflush=True, bind=engine))
    yield db_session
    db_session.close()
    connection.close()

Usage:

from mymodels import Foo

with db_session("sqlite://") as db:
    foos = db.query(Foo).all()
Berislav Lopac
la source
3
Y a-t-il une raison pour laquelle vous créez non seulement une nouvelle session, mais également une nouvelle connexion?
danqing
Pas vraiment - c'est un exemple rapide pour montrer le mécanisme, bien qu'il soit logique de tout créer de nouveau lors des tests, là où j'utilise le plus cette approche. Il devrait être facile d'étendre cette fonction avec la connexion comme argument facultatif.
Berislav Lopac