SQLAlchemy: Erstellen oder Wiederverwenden einer Sitzung

98

Nur eine kurze Frage: SQLAlchemy spricht davon , sessionmaker()einmal aufzurufen , aber die resultierende Session()Klasse jedes Mal aufzurufen, wenn Sie mit Ihrer Datenbank sprechen müssen. Für mich bedeutet das, dass ich in der Sekunde, in der ich meine erste session.add(x)oder etwas Ähnliches machen würde, die erste machen würde

from project import Session
session = Session()

Bisher habe ich den Aufruf session = Session()in meinem Modell einmal getätigt und dann immer dieselbe Sitzung an einer beliebigen Stelle in meiner Anwendung importiert. Da es sich um eine Webanwendung handelt, bedeutet dies normalerweise dasselbe (da eine Ansicht ausgeführt wird).

Aber wo ist der Unterschied? Was ist der Nachteil, wenn Sie immer nur eine Sitzung für meine Datenbank verwenden, bis meine Funktion abgeschlossen ist, und dann eine neue erstellen, wenn ich das nächste Mal mit meiner Datenbank sprechen möchte?

Wenn ich mehrere Threads verwende, sollte jeder seine eigene Sitzung erhalten. Aber mit stellen scoped_session()ich bereits sicher, dass das Problem nicht besteht, oder?

Bitte klären Sie, ob eine meiner Annahmen falsch ist.

Javex
quelle

Antworten:

222

sessionmaker()ist eine Fabrik, die dazu dient, Konfigurationsoptionen zum Erstellen neuer SessionObjekte an nur einem Ort zu platzieren. Es ist insofern optional, als Sie genauso einfach anrufen können, Session(bind=engine, expire_on_commit=False)wann immer Sie ein neues benötigen Session, mit der Ausnahme, dass es ausführlich und überflüssig ist, und ich wollte die Verbreitung kleiner "Helfer" stoppen, die sich jeweils mit dem Problem dieser Redundanz in einem neuen befassten und verwirrender Weg.

Es sessionmaker()ist also nur ein Werkzeug, mit dem Sie SessionObjekte erstellen können, wenn Sie sie benötigen.

Nächster Teil. Ich denke, die Frage ist, was ist der Unterschied zwischen dem Erstellen eines neuen Session()an verschiedenen Punkten und dem einfachen Verwenden eines neuen . Die Antwort nicht sehr. Sessionist ein Container für alle Objekte, die Sie in ihn einfügen, und verfolgt dann auch eine offene Transaktion. In dem Moment, in dem Sie rollback()oder aufrufen commit(), ist die Transaktion beendet und der Sessionhat keine Verbindung zur Datenbank, bis er aufgefordert wird, SQL erneut auszugeben. Die Links, die es zu Ihren zugeordneten Objekten enthält, sind schwache Verweise, vorausgesetzt, die Objekte sind frei von ausstehenden Änderungen. Selbst in dieser Hinsicht Sessionwird das System wieder in einen brandneuen Zustand versetzt, wenn Ihre Anwendung alle Verweise auf zugeordnete Objekte verliert. Wenn Sie die Standardeinstellung beibehalten"expire_on_commit"Einstellung, dann sind alle Objekte nach einem Commit abgelaufen. Wenn dies Sessionfünf oder zwanzig Minuten lang anhält und sich bei der nächsten Verwendung allerlei Dinge in der Datenbank geändert haben, wird beim nächsten Zugriff auf diese Objekte der brandneue Status geladen, obwohl sie sich im Speicher befinden für zwanzig Minuten.

In Webanwendungen sagen wir normalerweise: Hey, warum machst du nicht Sessionbei jeder Anfrage eine brandneue , anstatt immer wieder dieselbe zu verwenden? Diese Vorgehensweise stellt sicher, dass die neue Anforderung "sauber" beginnt. Wenn einige Objekte aus der vorherigen Anforderung noch nicht mit Müll gesammelt wurden und Sie sie möglicherweise deaktiviert haben "expire_on_commit", hängt möglicherweise noch ein Status aus der vorherigen Anforderung herum, und dieser Status ist möglicherweise sogar ziemlich alt. Wenn Sie darauf achten, eingeschaltet zu bleiben expire_on_commitund definitiv anzurufen commit()oder rollback()auf Anfrage zu enden, ist das in Ordnung, aber wenn Sie mit einem brandneuen Produkt beginnen Session, gibt es nicht einmal die Frage, ob Sie sauber anfangen. Also die Idee, jede Anfrage mit einer neuen zu beginnenSessionexpire_on_commitDies ist wirklich nur der einfachste Weg, um sicherzustellen, dass Sie neu beginnen, und um die Verwendung so gut wie optional zu machen, da dieses Flag für eine Operation, commit()die mitten in einer Reihe von Operationen aufgerufen wird, viel zusätzliches SQL verursachen kann . Ich bin mir nicht sicher, ob dies Ihre Frage beantwortet.

In der nächsten Runde erwähnen Sie das Einfädeln. Wenn Ihre App Multithreading ist, empfehlen wir, sicherzustellen, dass die Sessionverwendete App lokal für ... etwas ist. scoped_session()Standardmäßig ist es lokal für den aktuellen Thread. In einer Web-App ist die lokale Anforderung sogar noch besser. Flask-SQLAlchemy sendet tatsächlich eine benutzerdefinierte "Bereichsfunktion" an, scoped_session()damit Sie eine Sitzung mit Anforderungsbereich erhalten. Die durchschnittliche Pyramid-Anwendung fügt die Sitzung in die "Anforderungs" -Registrierung ein. Wenn Sie solche Schemata verwenden, scheint die Idee "Neue Sitzung auf Anfrage erstellen" weiterhin die einfachste Möglichkeit zu sein, die Dinge gerade zu halten.

zzzeek
quelle
17
Wow, dies beantwortet alle meine Fragen zum SQLAlchemy-Teil und fügt sogar einige Informationen über Flask and Pyramid hinzu! Zusätzlicher Bonus: Entwickler antworten;) Ich wünschte, ich könnte mehr als einmal abstimmen. Vielen Dank!
Javex
Eine Klarstellung, wenn möglich: Sie sagen, expire_on_commit "kann viel zusätzliches SQL verursachen" ... können Sie weitere Details angeben? Ich dachte, expire_on_commit betrifft nur das, was im RAM passiert, nicht das, was in der Datenbank passiert.
Veky
3
expire_on_commit kann zu mehr SQL führen, wenn Sie dieselbe Sitzung erneut verwenden und einige Objekte in dieser Sitzung noch herumhängen. Wenn Sie darauf zugreifen, erhalten Sie für jede einzelne ein einzeiliges SELECT, wenn sie einzeln aktualisiert werden ihren Zustand in Bezug auf die neue Transaktion.
Zzzeek
1
Hallo, @zzzeek. Vielen Dank für die hervorragende Antwort. Ich bin sehr neu in Python und möchte einige Dinge klarstellen: 1) Verstehe ich richtig, wenn ich eine neue "Sitzung" durch Aufrufen der Session () -Methode erstelle, wird eine SQL-Transaktion erstellt, und die Transaktion wird geöffnet, bis ich eine Sitzung festschreibe / zurücksetze ? 2) Verwendet session () eine Art Verbindungspool oder stellt jedes Mal eine neue Verbindung zu SQL her?
Alex Gurskiy
27

Zusätzlich zu der hervorragenden Antwort von zzzeek gibt es hier ein einfaches Rezept, um schnell wegwerfbare, in sich geschlossene Sitzungen zu erstellen:

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

Verwendung:

from mymodels import Foo

with db_session("sqlite://") as db:
    foos = db.query(Foo).all()
Berislav Lopac
quelle
3
Gibt es einen Grund, warum Sie nicht nur eine neue Sitzung erstellen, sondern auch eine neue Verbindung?
Danqing
Nicht wirklich - dies ist ein kurzes Beispiel, um den Mechanismus zu zeigen, obwohl es sinnvoll ist, beim Testen alles neu zu erstellen, wo ich diesen Ansatz am häufigsten verwende. Es sollte einfach sein, diese Funktion mit der Verbindung als optionalem Argument zu erweitern.
Berislav Lopac