SQLAlchemy:创建与重用会话

问题:SQLAlchemy:创建与重用会话

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

from project import Session
session = Session()

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

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

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

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

Just a quick question: SQLAlchemy talks about calling sessionmaker() once but calling the resulting Session() class each time you need to talk to your DB. For me that means the second I would do my first session.add(x) or something similar, I would first do

from project import Session
session = Session()

What I did until now was to make the call session = Session() in my model once and then always import the same session anywhere in my application. Since this is a web-applications this would usually mean the same (as one view is executed).

But where is the difference? What is the disadvantage of using one session all the time against using it for my database stuff until my function is done and then creating a new one the next time I want to talk to my DB?

I get that if I use multiple threads, each one should get their own session. But using scoped_session(), I already make sure that problem doesn’t exist, do I?

Please clarify if any of my assumptions are wrong.


回答 0

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应用程序会将会话粘贴到“请求”注册表中。当使用这样的方案时,“在请求开始时创建新会话”的想法仍然看起来像是使事情保持正直的最直接方法。

sessionmaker() is a factory, it’s there to encourage placing configuration options for creating new Session objects in just one place. It is optional, in that you could just as easily call Session(bind=engine, expire_on_commit=False) anytime you needed a new Session, except that its verbose and redundant, and I wanted to stop the proliferation of small-scale “helpers” that each approached the issue of this redundancy in some new and more confusing way.

So sessionmaker() is just a tool to help you create Session objects when you need them.

Next part. I think the question is, what’s the difference between making a new Session() at various points versus just using one all the way through. The answer, not very much. Session is a container for all the objects you put into it, and then it also keeps track of an open transaction. At the moment you call rollback() or commit(), the transaction is over, and the Session has no connection to the database until it is called upon to emit SQL again. The links it holds to your mapped objects are weak referencing, provided the objects are clean of pending changes, so even in that regard the Session will empty itself out back to a brand new state when your application loses all references to mapped objects. If you leave it with its default "expire_on_commit" setting, then all the objects are expired after a commit. If that Session hangs around for five or twenty minutes, and all kinds of things have changed in the database the next time you use it, it will load all brand new state the next time you access those objects even though they’ve been sitting in memory for twenty minutes.

In web applications, we usually say, hey why don’t you make a brand new Session on each request, rather than using the same one over and over again. This practice ensures that the new request begins “clean”. If some objects from the previous request haven’t been garbage collected yet, and if maybe you’ve turned off "expire_on_commit", maybe some state from the previous request is still hanging around, and that state might even be pretty old. If you’re careful to leave expire_on_commit turned on and to definitely call commit() or rollback() at request end, then it’s fine, but if you start with a brand new Session, then there’s not even any question that you’re starting clean. So the idea to start each request with a new Session is really just the simplest way to make sure you’re starting fresh, and to make the usage of expire_on_commit pretty much optional, as this flag can incur a lot of extra SQL for an operation that calls commit() in the middle of a series of operations. Not sure if this answers your question.

The next round is what you mention about threading. If your app is multithreaded, we recommend making sure the Session in use is local to…something. scoped_session() by default makes it local to the current thread. In a web app, local to the request is in fact even better. Flask-SQLAlchemy actually sends a custom “scope function” to scoped_session() so that you get a request-scoped session. The average Pyramid application sticks the Session into the “request” registry. When using schemes like these, the “create new Session on request start” idea continues to look like the most straightforward way to keep things straight.


回答 1

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

In addition to the excellent zzzeek’s answer, here’s a simple recipe to quickly create throwaway, self-enclosed sessions:

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()

Usage:

from mymodels import Foo

with db_session("sqlite://") as db:
    foos = db.query(Foo).all()