多线程SQLAlchemy网页应用中推荐的scoped_session使用模式是什么?
我正在用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 个回答
如果你每次请求都创建一个新的会话,而且每个请求都是由单个线程处理的,那么你就不需要创建一个特定范围的会话。
你需要调用 s.commit()
来把那些处于待处理状态的对象变成持久化的,也就是说,把更改保存到数据库里。
你可能还想通过调用 s.close()
来关闭会话。
没错,这样做是对的。
举个例子:
这个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)配合得也很好。