SQLAlchemy:创建与重用会话


98

只是一个简单的问题:SQLAlchemy的有关谈判调用sessionmaker()一次,但调用导致Session()每次你需要跟你的数据库的时间类。对我来说,这意味着第二个我会做我的第一个session.add(x)或类似的事情,我会先做

from project import Session
session = Session()

到目前为止,我所做的只是session = Session()在模型中进行一次调用,然后始终在应用程序中的任何位置导入相同的会话。由于这是一个Web应用程序,因此通常意味着相同(因为执行一个视图)。

但是区别在哪里?一直使用一个会话而不是在数据库中使用它直到我的函数完成,然后在下次我想与数据库对话时创建一个新会话的缺点是什么?

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

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

Answers:


223

sessionmaker()是一个工厂,它鼓励Session在一个地方放置用于创建新对象的配置选项。它是可选的,因为您可以Session(bind=engine, expire_on_commit=False)随时随地调用一个new Session,除了它冗长而冗长,而且我想阻止小规模的“助手”的泛滥,每个小助手都在某些新版本中解决了这种冗余的问题。和更令人困惑的方式。

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

下一部分。我认为问题是,Session()在各个点上制作一个新的文件与一直使用一个新文件之间有什么区别。答案不是很多。 Session是您放入其中的所有对象的容器,然后它还会跟踪未完成的事务。现在,您调用rollback()commit(),事务结束,并且Session与数据库没有连接,直到再次调用它发出SQL。它提供给映射对象的链接是弱引用,前提是这些对象可以清除未决的更改,因此即使在这种情况Session下,当应用程序丢失对映射对象的所有引用时,will也会将自身清空为全新状态。如果保留其默认值"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应用程序会将会话粘贴到“请求”注册表中。当使用这样的方案时,“在请求开始时创建新会话”的想法仍然看起来像是使事情保持正直的最直接方法。


17
哇,这回答了我有关SQLAlchemy部分的所有问题,甚至还添加了一些有关Flask和Pyramid的信息!增加的奖励:开发人员回答;)我希望我可以投票一次以上。非常感谢你!
javex

如果可能的话,做一个澄清:您说expire_on_commit“会产生很多额外的SQL” ...您能提供更多详细信息吗?我以为expire_on_commit只关注RAM中发生的事情,而不关注数据库中发生的事情。
Veky 2015年

3
如果再次使用同一会话,expire_on_commit可能会导致更多的SQL,并且某些对象仍在该会话中徘徊,当您访问它们时,您将获得针对每个对象的单行SELECT,因为它们每个都分别刷新他们在新交易中的状态。
zzzeek

1
嗨,@ zzzeek。感谢您的出色回答。我是python的新手,我想澄清以下几点:1)当我通过调用Session()方法创建新的“会话”来创建SQL事务时是否理解正确,然后将打开事务,直到提交/回滚会话为止?2)session()是否每次都使用某种连接池或与sql建立新连接?
亚历克斯·古斯基

27

除了出色的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()

3
您为什么不仅创建一个新会话,而且还创建一个新的连接?
丹青

并非如此-这是一个展示该机制的简单示例,尽管在测试中创建所有新鲜事物是有意义的,而我最常使用这种方法。使用连接作为可选参数扩展此功能应该很容易。
贝里斯拉夫·洛帕克
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.