131

只是一个简单的问题:SQLAlchemy谈论调用sessionmaker()一次,但Session()每次需要与数据库交谈时都会调用结果类。对我来说,这意味着第二个我会做我的第一个session.add(x)或类似的事情,我会先做

from project import Session
session = Session()

到目前为止,我所做的是session = Session()在我的模型中调用一次,然后总是在我的应用程序的任何地方导入相同的会话。由于这是一个网络应用程序,这通常意味着相同(执行一个视图时)。

但区别在哪里?一直使用一个会话而不是在我的功能完成之前将其用于我的数据库内容,然后在下次我想与我的数据库交谈时创建一个新会话有什么缺点?

我知道如果我使用多个线程,每个线程都应该有自己的会话。但是使用scoped_session(),我已经确保该问题不存在,不是吗?

请澄清我的任何假设是否错误。

4

2 回答 2

295

sessionmaker()是一个工厂,它鼓励Session在一个地方放置用于创建新对象的配置选项。它是可选的,因为您可以在Session(bind=engine, expire_on_commit=False)需要新的任何时候轻松调用Session,除了它的冗长和冗余,我想阻止小规模“帮助者”的扩散,每个帮助者都在一些新的问题中解决了这种冗余问题和更令人困惑的方式。

因此sessionmaker(),它只是一个工具,可帮助您Session在需要时创建对象。

下一部分。Session()我认为问题是,在不同的点上制作一个新的与一直使用一个有什么区别。答案,不是很多。 Session是您放入其中的所有对象的容器,然后它还跟踪打开的事务。rollback()在您调用or的那一刻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()默认情况下,它对当前线程是本地的。在 Web 应用程序中,请求的本地化实际上更好。Flask-SQLAlchemy 实际上会发送一个自定义的“范围函数”,scoped_session()以便您获得一个请求范围的会话。普通的 Pyramid 应用程序将 Session 粘贴到“请求”注册表中。当使用这样的方案时,“在请求开始时创建新会话”的想法仍然看起来是最直接的方式来保持直截了当。

于 2012-08-31T23:45:40.017 回答
36

除了出色的 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()
于 2015-11-11T10:22:36.820 回答