2012-08-31 17 views
59

Solo una pregunta rápida: SQLAlchemy talks about llamando al sessionmaker() una vez pero llamando a la clase resultante Session() cada vez que necesite hablar con su base de datos. Para mí eso significa que el segundo lo haría mi primera session.add(x) o algo similar, me gustaría primero hacerSQLAlchemy: Crear vs. Reutilizar una sesión

from project import Session 
session = Session() 

Lo que hice hasta ahora era para hacer la llamada session = Session() en mi modelo vez y luego siempre importar la misma sesión en cualquier lugar de mi aplicación. Como se trata de una aplicación web, esto sería , generalmente significa lo mismo (como se ejecuta una vista).

¿Pero dónde está la diferencia? ¿Cuál es la desventaja de usar una sesión todo el tiempo contra su uso para mi material de base de datos hasta que mi función esté lista y luego crear una nueva la próxima vez que quiera hablar con mi DB?

Me sale que si uso múltiples hilos, cada uno debería tener su propia sesión. Pero usando scoped_session(), ya me aseguro de que el problema no exista, ¿verdad?

Aclare si alguna de mis suposiciones es incorrecta.

Respuesta

152

sessionmaker() es una fábrica, está ahí para alentar la colocación de opciones de configuración para crear nuevos objetos Session en un solo lugar. Es opcional, en el sentido de que puede llamar al Session(bind=engine, expire_on_commit=False) cada vez que necesite un nuevo Session, excepto que es detallado y redundante, y quería detener la proliferación de "ayudantes" a pequeña escala que abordaban el problema de esta redundancia en una forma nueva y más confusa.

Así que sessionmaker() es solo una herramienta para ayudarte a crear objetos Session cuando los necesitas.

Siguiente parte. Creo que la pregunta es, ¿cuál es la diferencia entre hacer un nuevo Session() en varios puntos en lugar de simplemente usar uno en todos los sentidos? La respuesta, no mucho. Session es un contenedor para todos los objetos que pone en él, y luego también realiza un seguimiento de una transacción abierta. En el momento en que llame al rollback() o al commit(), la transacción ha finalizado y el Session no tiene conexión con la base de datos hasta que se le solicite que emita SQL nuevamente. Los enlaces que contiene a los objetos asignados son referencias débiles, siempre que los objetos estén limpios de cambios pendientes, por lo que incluso en ese sentido, el Session se vaciará a un nuevo estado cuando la aplicación pierda todas las referencias a objetos mapeados. Si lo deja con su configuración predeterminada "expire_on_commit", todos los objetos caducarán después de una confirmación. Si ese Session se queda durante cinco o veinte minutos, y todo tipo de cosas han cambiado en la base de datos la próxima vez que lo use, cargará todos los nuevos estados la próxima vez que acceda a esos objetos aunque hayan estado sentados en memoria durante veinte minutos.

En las aplicaciones web, generalmente decimos, hey por qué no hace una nueva marca Session en cada solicitud, en lugar de utilizar la misma una y otra vez. Esta práctica asegura que la nueva solicitud comienza "limpia". Si algunos objetos de la solicitud anterior aún no han sido recogidos basura, y si tal vez ha desactivado "expire_on_commit", tal vez algún estado de la solicitud anterior todavía está dando vueltas, y ese estado podría ser bastante viejo. Si tiene cuidado de dejar expire_on_commit encendido y definitivamente llamar al commit() o rollback() al final de la solicitud, entonces está bien, pero si comienza con un nuevo Session, entonces ni siquiera hay dudas de que está comenzando a limpiar.Entonces, la idea de comenzar cada solicitud con un nuevo Session es realmente la manera más simple de asegurarse de que está comenzando de cero, y hacer que el uso de expire_on_commit sea prácticamente opcional, ya que este indicador puede generar una gran cantidad de SQL adicionales para una operación que llama al commit() en el medio de una serie de operaciones. No estoy seguro si esto responde tu pregunta.

La siguiente ronda es lo que mencionas sobre el enhebrado. Si su aplicación es multiproceso, le recomendamos que se asegure de que el Session en uso sea local para ... algo. scoped_session() por defecto lo hace local al hilo actual. En una aplicación web, lo local de la solicitud es, de hecho, incluso mejor. Flask-SQLAlchemy en realidad envía una "función de alcance" personalizada al scoped_session() para que pueda obtener una sesión con alcance de solicitud. La aplicación Pyramid promedio pega la sesión en el registro de "solicitud". Cuando se utilizan esquemas como estos, la idea de "crear nueva sesión al inicio de la solicitud" sigue pareciendo la forma más directa de mantener las cosas en orden.

+9

Guau, esto responde todas mis preguntas en la parte de SQLAlchemy e incluso agrega alguna información acerca de Flask a ¡nd Pyramid! Bonificación adicional: los desarrolladores responden;) Desearía poder votar más de una vez. ¡Muchas gracias! – javex

+0

Una aclaración, si es posible: dices expire_on_commit "puede incurrir en un montón de SQL adicional" ... ¿puedes dar más detalles? Pensé que expire_on_commit solo se refería a lo que sucede en la RAM, no a lo que sucede en la base de datos. – Veky

+2

expire_on_commit puede dar como resultado más SQL si vuelve a utilizar la misma sesión de nuevo, y algunos objetos todavía están dando vueltas en esa sesión, cuando accede a ellos obtendrá un SELECT de una sola fila para cada uno de ellos, ya que cada uno individualmente actualizar su estado en términos de la nueva transacción. – zzzeek

12

Además de la respuesta de la excelente zzzeek, ​​he aquí una receta sencilla para crear rápidamente, encerradas en sí mismas sesiones de usar y tirar:

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

Uso:

from mymodels import Foo 

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

¿Existe alguna razón por la que cree no solo una nueva sesión, sino también una conexión nueva? – aforaudrey

+0

No realmente, este es un ejemplo rápido para mostrar el mecanismo, aunque tiene sentido crear todo lo nuevo en las pruebas, donde uso este enfoque al máximo. Debería ser fácil expandir esta función con la conexión como un argumento opcional. –

-1

Puede crear la sesión utilizando el PP

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

es 'Frasco' específico, no es común 'SQLAlchemy' –

Cuestiones relacionadas