只是一个简单的问题:SQLAlchemy谈到调用sessionmaker()
一次,但每次需要与DB对话时都会调用生成的Session()
类。对我来说,这意味着第二次我会做我的第一个session.add(x)
或类似的事情,我会先做
from project import Session
session = Session()
到目前为止,我所做的是在我的模型中调用session = Session()
一次,然后总是在应用程序中的任何位置导入相同的会话。由于这是一个web应用程序,因此通常的含义相同(执行一个视图时)。
但区别在哪里呢?在我的功能完成之前,一直使用一个会话而不是将其用于我的数据库,然后在下次我想与我的数据库交谈时创建一个新会话的缺点是什么?
我知道,如果我使用多个线程,每个线程都应该有自己的会话。但使用scoped_session()
,我已经确保了这个问题不存在,是吗?
请澄清我的任何假设是否错误。
sessionmaker()
是一个工厂,它鼓励将用于创建新Session
对象的配置选项放在一个地方。它是可选的,因为您可以在需要新的Session
的任何时候轻松地调用Session(bind=engine, expire_on_commit=False)
,除了它冗长和冗余之外,我想阻止小规模的"助手"的激增,每个人都以某种新的、更令人困惑的方式处理这种冗余问题。
因此,sessionmaker()
只是一个工具,可以帮助您在需要时创建Session
对象。
下一部分。我认为问题是,在不同的点上制作一个新的Session()
与始终使用一个有什么区别。答案不多。Session
是您放入其中的所有对象的容器,然后它还跟踪打开的事务。在调用rollback()
或commit()
的那一刻,事务已经结束,并且Session
在被调用以再次发出SQL之前没有与数据库的连接。只要对象没有挂起的更改,它与映射对象的链接就是弱引用,所以即使在这方面,当应用程序丢失对映射对象的所有引用时,Session
也会清空自己,回到一个全新的状态。如果将其保留为默认的"expire_on_commit"
设置,则所有对象在提交后都将过期。如果Session
挂起五到二十分钟,并且下次使用时数据库中的所有内容都发生了变化,那么下次访问这些对象时,它将加载所有全新的状态,即使它们已经在内存中停留了二十分钟。
在web应用程序中,我们通常会说,嘿,为什么不为每个请求制作一个全新的Session
,而不是一遍又一遍地使用同一个呢。这种做法可确保新请求以"干净"开头。如果上一个请求中的一些对象还没有被垃圾收集,并且您可能已经关闭了"expire_on_commit"
,那么上一个要求中的一些状态可能仍然存在,并且该状态甚至可能非常旧。如果你小心地打开expire_on_commit
,并在请求结束时明确地调用commit()
或rollback()
,那么这很好,但如果你从一个全新的Session
开始,那么你就不会有任何开始清理的问题。因此,用一个新的Session
启动每个请求实际上是确保重新启动并使expire_on_commit
的使用变得非常可选的最简单的方法,因为这个标志可能会导致在一系列操作中间调用commit()
的操作产生大量额外的SQL。不确定这是否回答了你的问题。
下一轮是你提到的线程。如果你的应用程序是多线程的,我们建议确保使用中的Session
是本地的。默认情况下,scoped_session()
使其成为当前线程的本地。事实上,在网络应用程序中,请求的本地性甚至更好。Flask SQLAlchemy实际上向scoped_session()
发送了一个自定义的"范围函数",这样您就可以获得一个请求范围的会话。平均Pyramid应用程序会将会话粘贴到"请求"注册表中。当使用这样的方案时,"在请求启动时创建新会话"的想法看起来仍然是最直接的方法。
除了出色的zzzeek回答之外,这里还有一个快速创建一次性、自我封闭会话的简单方法:
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()
用法:
from mymodels import Foo
with db_session("sqlite://") as db:
foos = db.query(Foo).all()