SQLAlchemy: creazione e riutilizzo di una sessione


98

Solo una domanda veloce: SQLAlchemy parla di chiamare sessionmaker()una volta ma di chiamare la Session()classe risultante ogni volta che devi parlare con il tuo DB. Per me questo significa che il secondo in cui farei il mio primo session.add(x)o qualcosa di simile, lo farei per primo

from project import Session
session = Session()

Quello che ho fatto fino ad ora è stato effettuare la chiamata session = Session()nel mio modello una volta e poi importare sempre la stessa sessione ovunque nella mia applicazione. Poiché si tratta di un'applicazione Web, di solito significa lo stesso (poiché viene eseguita una vista).

Ma dov'è la differenza? Qual è lo svantaggio di utilizzare una sessione tutto il tempo rispetto a usarla per il mio database fino al termine della mia funzione e quindi crearne una nuova la prossima volta che voglio parlare con il mio DB?

Capisco che se utilizzo più thread, ognuno dovrebbe avere la propria sessione. Ma usando scoped_session(), mi assicuro già che il problema non esista, vero?

Si prega di chiarire se qualcuno dei miei presupposti è sbagliato.

Risposte:


225

sessionmaker()è una fabbrica, è lì per incoraggiare l'inserimento di opzioni di configurazione per la creazione di nuovi Sessionoggetti in un unico posto. È facoltativo, in quanto potresti facilmente chiamare Session(bind=engine, expire_on_commit=False)ogni volta che ne avevi bisogno di un nuovo Session, tranne che è prolisso e ridondante, e volevo fermare la proliferazione di "aiutanti" su piccola scala che ognuno ha affrontato il problema di questa ridondanza in qualche nuovo e modo più confuso.

Quindi sessionmaker()è solo uno strumento per aiutarti a creare Sessionoggetti quando ne hai bisogno.

Parte successiva. Penso che la domanda sia: qual è la differenza tra crearne uno nuovo Session()in vari punti e usarne uno fino in fondo. La risposta, non molto. Sessionè un contenitore per tutti gli oggetti che ci metti e quindi tiene traccia anche di una transazione aperta. Nel momento in cui chiami rollback()o commit(), la transazione è terminata e Sessionnon ha connessione al database finché non viene chiamato a emettere nuovamente SQL. I collegamenti che mantiene agli oggetti mappati sono riferimenti deboli, a condizione che gli oggetti siano privi di modifiche in sospeso, quindi anche a questo riguardo il dispositivo Sessionsi svuoterà di nuovo in uno stato completamente nuovo quando l'applicazione perde tutti i riferimenti agli oggetti mappati. Se lo lasci con il suo valore predefinito"expire_on_commit"l'impostazione, quindi tutti gli oggetti scadono dopo un commit. Se Sessionrimane in sospeso per cinque o venti minuti e ogni tipo di cose sono cambiate nel database la prossima volta che lo usi, caricherà tutto lo stato nuovo di zecca la prossima volta che accedi a quegli oggetti anche se sono rimasti in memoria per venti minuti.

Nelle applicazioni web, di solito diciamo, ehi, perché non ne crei uno nuovo Sessionsu ogni richiesta, invece di utilizzare lo stesso più e più volte. Questa pratica garantisce che la nuova richiesta inizi "pulita". Se alcuni oggetti della richiesta precedente non sono stati ancora raccolti in modo indesiderato e se forse li hai disattivati "expire_on_commit", forse uno stato della richiesta precedente è ancora in giro e quello stato potrebbe anche essere piuttosto vecchio. Se stai attento a lasciare expire_on_commitacceso e chiamare definitivamente commit()o rollback()alla fine della richiesta, allora va bene, ma se inizi con un nuovo di zecca Session, allora non c'è nemmeno il dubbio che stai iniziando pulito. Quindi l'idea di iniziare ogni richiesta con una nuovaSessionè davvero solo il modo più semplice per assicurarti di iniziare da zero e per rendere l'uso di expire_on_commitpraticamente opzionale, poiché questo flag può comportare un sacco di SQL extra per un'operazione che chiama commit()nel mezzo di una serie di operazioni. Non sono sicuro che questo risponda alla tua domanda.

Il prossimo round è quello che hai menzionato sul threading. Se la tua app è multithread, ti consigliamo di assicurarti che quella Sessionin uso sia locale per ... qualcosa. scoped_session()per impostazione predefinita, lo rende locale al thread corrente. In un'app web, localmente alla richiesta è addirittura migliore. Flask-SQLAlchemy invia effettivamente una "funzione di ambito" personalizzata in scoped_session()modo da ottenere una sessione con ambito di richiesta. L'applicazione Pyramid media inserisce la Session nel registro delle "richieste". Quando si utilizzano schemi come questi, l'idea di "creare una nuova sessione su richiesta di avvio" continua a sembrare il modo più semplice per mantenere le cose chiare.


17
Wow, questo risponde a tutte le mie domande sulla parte SQLAlchemy e aggiunge anche alcune informazioni su Flask e Pyramid! Bonus aggiunto: gli sviluppatori rispondono;) Vorrei poter votare più di una volta. Grazie mille!
javex

Una precisazione, se possibile: dici expire_on_commit "può comportare un sacco di SQL extra" ... puoi fornire maggiori dettagli? Pensavo che expire_on_commit riguardasse solo ciò che accade nella RAM, non ciò che accade nel database.
Veky

3
expire_on_commit può risultare in più SQL se riutilizzi di nuovo la stessa sessione e alcuni oggetti sono ancora in giro in quella sessione, quando accedi a essi otterrai una singola riga SELECT per ciascuno di essi mentre si aggiornano singolarmente il loro stato in termini di nuova transazione.
zzzeek

1
Ciao, @zzzeek. Grazie per l'eccellente risposta. Sono molto nuovo in Python e voglio chiarire diverse cose: 1) Capisco bene quando creo una nuova "sessione" chiamando il metodo Session () creerà una transazione SQL, quindi la transazione verrà aperta fino a quando non effettuo il commit / rollback della sessione ? 2) session () utilizza un qualche tipo di pool di connessioni o effettua una nuova connessione a sql ogni volta?
Alex Gurskiy

27

Oltre all'eccellente risposta di zzzeek, ​​ecco una semplice ricetta per creare rapidamente sessioni monouso e chiuse:

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

Utilizzo:

from mymodels import Foo

with db_session("sqlite://") as db:
    foos = db.query(Foo).all()

3
C'è un motivo per cui crei non solo una nuova sessione, ma anche una nuova connessione?
danqing

Non proprio: questo è un rapido esempio per mostrare il meccanismo, anche se ha senso creare tutto nuovo durante i test, dove uso maggiormente questo approccio. Dovrebbe essere facile espandere questa funzione con la connessione come argomento opzionale.
Berislav Lopac
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.