SQLAlchemy:创建与重用会话

时间:2021-08-07 16:06:09

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

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

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

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

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?

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

Please clarify if any of my assumptions are wrong.

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

3 个解决方案

#1


157  

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.

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

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

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

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.

下一部分。我认为问题是,在不同的点上创建一个新的Session()与在整个过程中使用一个Session之间有什么区别。答案,不是很多。 Session是您放入其中的所有对象的容器,然后它还跟踪打开的事务。在您调用rollback()或commit()时,事务已结束,并且Session在没有连接到数据库,直到它被调用再次发出SQL。它保存到映射对象的链接是弱引用,前提是对象没有待处理的更改,因此即使在这方面,当应用程序丢失对映射对象的所有引用时,Session也会将其自身清空回到全新状态。如果保留默认的“expire_on_commit”设置,则提交后所有对象都将过期。如果该会话挂起五到二十分钟,并且下次使用它时数据库中的各种事情都发生了变化,那么下次访问这些对象时它会加载所有全新状态,即使它们已经坐在记忆二十分钟。

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.

在Web应用程序中,我们通常会说,嘿,为什么不在每个请求上创建一个全新的Session,而不是一遍又一遍地使用相同的Session。这种做法可确保新请求开始“干净”。如果前一个请求中的某些对象尚未被垃圾收集,并且如果您关闭了“expire_on_commit”,那么前一个请求中的某些状态可能仍然存在,并且该状态甚至可能已经过时了。如果你小心地打开expire_on_commit并且在请求结束时肯定调用commit()或rollback(),那么它很好,但如果你从一个全新的Session开始,那么你甚至没有任何问题要开始清洁。因此,使用新Session启动每个请求的想法实际上只是确保您重新开始的最简单方法,并且使expire_on_commit的使用几乎是可选的,因为此标志可能会导致大量额外的SQL操作在一系列操作中调用commit()。不确定这是否回答了你的问题。

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.

下一轮就是你提到的线程问题。如果您的应用是多线程的,我们建议您确保使用的会话是本地的......某事。默认情况下,scoped_session()使其成为当前线程的本地。在Web应用程序中,请求的本地实际上甚至更好。 Flask-SQLAlchemy实际上向scoped_session()发送自定义“范围函数”,以便您获得请求范围的会话。平均Pyramid应用程序将会话粘贴到“请求”注册表中。当使用这样的方案时,“创建新的会话请求开始”的想法继续看起来像是最简单的方法来保持正确。

#2


14  

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

除了优秀的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()

Usage:

用法:

from mymodels import Foo

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

#3


-1  

You can create the session using the db

您可以使用db创建会话

db = SQLAlchemy(app)
engine = db.engine
Session = sessionmaker(engine)
session = Session()

#1


157  

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.

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

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

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

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.

下一部分。我认为问题是,在不同的点上创建一个新的Session()与在整个过程中使用一个Session之间有什么区别。答案,不是很多。 Session是您放入其中的所有对象的容器,然后它还跟踪打开的事务。在您调用rollback()或commit()时,事务已结束,并且Session在没有连接到数据库,直到它被调用再次发出SQL。它保存到映射对象的链接是弱引用,前提是对象没有待处理的更改,因此即使在这方面,当应用程序丢失对映射对象的所有引用时,Session也会将其自身清空回到全新状态。如果保留默认的“expire_on_commit”设置,则提交后所有对象都将过期。如果该会话挂起五到二十分钟,并且下次使用它时数据库中的各种事情都发生了变化,那么下次访问这些对象时它会加载所有全新状态,即使它们已经坐在记忆二十分钟。

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.

在Web应用程序中,我们通常会说,嘿,为什么不在每个请求上创建一个全新的Session,而不是一遍又一遍地使用相同的Session。这种做法可确保新请求开始“干净”。如果前一个请求中的某些对象尚未被垃圾收集,并且如果您关闭了“expire_on_commit”,那么前一个请求中的某些状态可能仍然存在,并且该状态甚至可能已经过时了。如果你小心地打开expire_on_commit并且在请求结束时肯定调用commit()或rollback(),那么它很好,但如果你从一个全新的Session开始,那么你甚至没有任何问题要开始清洁。因此,使用新Session启动每个请求的想法实际上只是确保您重新开始的最简单方法,并且使expire_on_commit的使用几乎是可选的,因为此标志可能会导致大量额外的SQL操作在一系列操作中调用commit()。不确定这是否回答了你的问题。

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.

下一轮就是你提到的线程问题。如果您的应用是多线程的,我们建议您确保使用的会话是本地的......某事。默认情况下,scoped_session()使其成为当前线程的本地。在Web应用程序中,请求的本地实际上甚至更好。 Flask-SQLAlchemy实际上向scoped_session()发送自定义“范围函数”,以便您获得请求范围的会话。平均Pyramid应用程序将会话粘贴到“请求”注册表中。当使用这样的方案时,“创建新的会话请求开始”的想法继续看起来像是最简单的方法来保持正确。

#2


14  

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

除了优秀的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()

Usage:

用法:

from mymodels import Foo

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

#3


-1  

You can create the session using the db

您可以使用db创建会话

db = SQLAlchemy(app)
engine = db.engine
Session = sessionmaker(engine)
session = Session()