SQLAlchemy:创建会话与重用会话



只是一个简单的问题: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()

最新更新