多线程SQLAlchemy网页应用中推荐的scoped_session使用模式是什么?

33 投票
2 回答
22014 浏览
提问于 2025-04-16 15:05

我正在用Python和sqlalchemy-0.7写一个应用程序。首先,我会初始化sqlalchemy的ORM(对象关系映射),然后启动一个多线程的网络服务器。目前我在用web.py来快速原型开发,但将来可能会换其他的。之后,我还会添加其他“线程”来处理定时任务,可能会使用其他的Python线程。

根据SA的文档,我了解到我需要使用scoped_session()来获取一个线程本地的会话,所以我的web.py应用程序应该大致是这样的:

import web
from myapp.model import Session  # scoped_session(sessionmaker(bind=engine))
from myapp.model import This, That, AndSoOn
urls = blah...
app  = web.application(urls, globals())

class index:
    def GET(self):
        s = Session()
        # get stuff done
        Session().remove()
        return(stuff)

class foo:
    def GET(self):
        s = Session()
        # get stuff done
        Session().remove()
        return(stuff)

这样处理会话对吗?

根据我的理解,我应该在每个方法中获取一个scoped_session,因为这样能让我得到一个线程本地的会话,而不是在模块级别就获取的那种。

另外,我应该在每个方法结束时调用.remove()或.commit(),或者类似的东西,否则会话里会保留一些持久化的对象,这样我就不能在其他线程中查询或访问这些对象了?

如果这个模式是正确的,或许可以通过只写一次来改进,比如使用一个装饰器?这样的装饰器可以获取会话,调用方法,然后确保正确处理会话。那样的话,怎么把会话传递给被装饰的函数呢?

2 个回答

2

如果你每次请求都创建一个新的会话,而且每个请求都是由单个线程处理的,那么你就不需要创建一个特定范围的会话。

你需要调用 s.commit() 来把那些处于待处理状态的对象变成持久化的,也就是说,把更改保存到数据库里。

你可能还想通过调用 s.close() 来关闭会话。

24

没错,这样做是对的。

举个例子:

这个Flask 微框架配合Flask-sqlalchemy扩展,正好实现了你说的功能。它还会在每次HTTP请求结束时自动调用.remove(),这样当前线程就会释放会话。单单调用.commit()是不够的,你还得用.remove()。

如果不使用Flask的视图函数,我通常会用一个“with”语句:

@contextmanager
def get_db_session():
    try:
        yield session
    finally:
        session.remove()

with get_db_session() as session:
    # do something with session

你也可以创建一个类似的装饰器。

这种作用域会话会创建一个数据库连接池,所以这种方法比每次HTTP请求都打开/关闭会话要快得多。它和绿色线程(比如gevent或eventlet)配合得也很好。

撰写回答