SQLAlchemy: 创建与重用会话

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

from project import Session
session = Session()

到目前为止,我所做的是在我的模型 一次中调用 session = Session(),然后总是在我的应用程序的任何地方导入相同的会话。因为这是一个 web 应用程序,所以 通常的意思是一样的(就像执行一个视图)。

但有什么区别呢?在函数完成之前一直使用一个会话而不是在数据库中使用它,然后在下次我想与数据库通信时创建一个新的会话,这样做的缺点是什么?

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

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

45354 次浏览

sessionmaker()是一个工厂,它鼓励在一个地方放置配置选项来创建新的 Session对象。它是可选的,因为只要你需要一个新的 Session,你就可以随时调用 Session(bind=engine, expire_on_commit=False),除了它的冗长和冗余,我想阻止小规模“帮助程序”的扩散,这些“帮助程序”都以一些新的和更令人困惑的方式处理这个冗余问题。

所以 sessionmaker()只是一个工具,帮助您在需要的时候创建 Session对象。

下一步。我认为问题是,在不同的时间点制作一个新的 Session()和全程使用一个 Session()有什么区别。答案,不是很清楚。Session是您放入其中的所有对象的容器,它还跟踪打开的事务。在您调用 rollback()commit()时,事务已经结束,而 Session与数据库没有连接,直到再次调用它发出 SQL。如果对象没有挂起的更改,那么它与映射对象之间的链接就是弱引用,因此即使在这方面,当应用程序丢失对映射对象的所有引用时,Session也会将自己清空回一个全新的状态。如果将其保留为默认的 "expire_on_commit"设置,则所有对象在提交后都会过期。如果这个 Session在数据库中停留了5到20分钟,并且下次使用时数据库中的所有内容都发生了变化,那么下次访问这些对象时,它将加载所有全新的状态,即使这些对象已经在内存中停留了20分钟。

在 Web 应用程序中,我们通常会说,为什么不针对每个请求创建一个全新的 Session,而不是一遍又一遍地使用同一个 Session。这种做法可以确保新请求开始时是“干净的”。如果来自前一个请求的某些对象尚未被垃圾收集,并且如果您已经关闭了 "expire_on_commit",那么来自前一个请求的某些状态可能仍然存在,而且该状态甚至可能非常老。如果你小心地让 expire_on_commit保持开启状态,并且在请求结束时明确地调用 commit()rollback(),那么这是可以的,但是如果你从一个全新的 Session开始,那么就不会有任何问题,你开始干净。因此,用一个新的 Session来启动每个请求的想法实际上是确保重新启动的最简单方法,而且使用 expire_on_commit几乎是可选的,因为对于在一系列操作中调用 commit()的操作来说,这个标志可能会产生大量额外的 SQL。不知道这是否回答了你的问题。

下一轮是你提到的线程。如果您的应用程序是多线程的,我们建议确保使用的 Session是本地的... ... 东西。默认情况下,scoped_session()使其成为当前线程的本地线程。在 Web 应用程序中,本地的请求实际上更好。Flask-SQLAlchemy 实际上向 scoped_session()发送一个自定义的“范围函数”,以便您获得一个请求范围的会话。一般的金字塔应用程序会将 Session 粘贴到“请求”注册表中。当使用这样的方案时,“在请求启动时创建新会话”的想法看起来仍然是保持事情正常的最直接的方法。

除了极好的 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()