2012-08-31 5 views
59

Solo una breve domanda: SQLAlchemy chiama una volta sola la classe risultante ogni volta che è necessario parlare con il database. Per me questo significa che il secondo che avrei fatto il mio primo session.add(x) o qualcosa di simile, vorrei prima fareSQLAlchemy: creazione e riutilizzo di una sessione

from project import Session 
session = Session() 

Quello che ho fatto fino ad ora è stato quello di effettuare la chiamata session = Session() nel mio modello volta e quindi importare sempre lo stesso sessione ovunque nella mia applicazione. Poiché si tratta di applicazioni Web, questo sarebbe in genere lo stesso (come viene eseguita una vista).

Ma dov'è la differenza? Qual è lo svantaggio di usare sempre una sessione contro l'utilizzo per il mio materiale di database fino a quando la mia funzione è terminata e quindi ne creo una nuova la prossima volta che voglio parlare con il mio DB?

Ho capito che se uso più thread, ognuno dovrebbe ottenere 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.

risposta

152

sessionmaker() è una fabbrica, è lì per incoraggiare l'immissione di opzioni di configurazione per la creazione di nuovi oggetti Session in un unico posto. È facoltativo, in quanto è possibile chiamare semplicemente Session(bind=engine, expire_on_commit=False) ogni volta che è necessario un nuovo Session, tranne che è prolisso e dettagliato, e volevo fermare la proliferazione di "helper" su piccola scala che ognuno si avvicinava al problema di questa ridondanza in un modo nuovo e più confuso.

Quindi sessionmaker() è solo uno strumento che consente di creare oggetti Session quando necessario.

parte successiva. Penso che la domanda sia: qual è la differenza tra creare un nuovo Session() in vari punti rispetto a usarne uno completamente. La risposta, non molto. Session è un contenitore per tutti gli oggetti che hai inserito e quindi tiene anche traccia di una transazione aperta. Al momento si chiama rollback() o commit(), la transazione è finita, e il Session non ha alcuna connessione al database fino a quando non è chiamato ad emettere nuovo SQL. I collegamenti in suo possesso ai vostri oggetti mappati sono riferimenti deboli, a condizione che gli oggetti sono puliti di modifiche in sospeso, quindi, anche a questo proposito il Session sarà svuotarsi sul retro ad una marca stato di nuovo quando l'applicazione perde tutti i riferimenti agli oggetti mappati. Se lo lasci con l'impostazione predefinita "expire_on_commit", tutti gli oggetti sono scaduti dopo un commit. Se questo Session si blocca in giro per cinque o venti minuti e tutti i tipi di cose sono cambiati nel database la volta successiva che lo utilizzerai, caricherà tutto lo stato nuovo di zecca la prossima volta che accederai a quegli oggetti anche se sono stati seduti in memoria per venti minuti.

Nelle applicazioni Web, di solito diciamo, hey, perché non si crea un nuovo Session su ogni richiesta, invece di utilizzare sempre la stessa. Questa pratica assicura che la nuova richiesta inizi "pulita". Se alcuni oggetti della richiesta precedente non sono ancora stati raccolti, e se forse hai disattivato "expire_on_commit", forse lo stato della richiesta precedente è ancora in agguato, e quello stato potrebbe anche essere piuttosto vecchio. Se stai attento a lasciare expire_on_commit acceso e chiamare definitivamente lo commit() o il rollback() alla fine della richiesta, allora va bene, ma se inizi con un nuovo Session, allora non c'è nemmeno nessuna domanda che stai iniziando a pulire.Quindi l'idea di iniziare ogni richiesta con un nuovo Session è davvero il modo più semplice per assicurarsi che tu stia iniziando di nuovo, e di rendere l'uso di expire_on_commit praticamente facoltativo, poiché questo flag può comportare un sacco di SQL aggiuntivo per un'operazione che chiama commit() nel mezzo di una serie di operazioni. Non sono sicuro se questo risponde alla tua domanda.

Il prossimo round è ciò che si parla di threading. Se la tua app è multithread, ti consigliamo di assicurarti che lo Session in uso sia locale per ... qualcosa. scoped_session() per impostazione predefinita lo rende locale al thread corrente. In un'app Web, la richiesta locale è in realtà ancora migliore. Flask-SQLAlchemy invia effettivamente una "funzione di ambito" personalizzata a scoped_session() in modo da ottenere una sessione con ambito richiesta. L'applicazione piramide media attacca la sessione nel registro "richiesta". Quando si utilizzano schemi come questi, l'idea "crea nuova sessione su richiesta di avvio" continua a sembrare il modo più semplice per mantenere le cose dritte.

+9

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

+0

Un chiarimento, se possibile: tu dici expire_on_commit "può incorrere in un sacco di extra SQL" ... puoi dare maggiori dettagli? Pensavo che expire_on_commit riguardasse solo ciò che accade nella RAM, non ciò che accade nel database. – Veky

+2

expire_on_commit può risultare in più SQL se riutilizzi di nuovo la stessa Session, e alcuni oggetti sono ancora in giro in quella Session, quando li accederai avrai una SELECT a riga singola per ognuno di essi poiché ciascuno di essi individualmente aggiornare il loro stato in termini di nuova transazione. – zzzeek

12

Oltre alla risposta eccellente del zzzeek, ​​ecco una ricetta semplice per creare rapidamente usa e getta, chiuso in se stesso sessioni:

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

C'è una ragione per cui non crei solo una nuova sessione, ma anche una nuova connessione? – aforaudrey

+0

Non proprio - questo è un rapido esempio per mostrare il meccanismo, anche se ha senso creare tutto fresco in testing, dove uso questo approccio di più. Dovrebbe essere facile espandere questa funzione con la connessione come argomento opzionale. –

-1

È possibile creare la sessione utilizzando il db

db = SQLAlchemy(app) 
engine = db.engine 
Session = sessionmaker(engine) 
session = Session() 
+3

è 'SQLAlchemy' 'Flask' ​​specifico, non comune' –