数据库连接泄漏:PostgreSQL、SQLAlchemy、Flask
我在使用PostgreSQL 9.3和SQLAlchemy 0.8.2的时候,发现数据库连接出现了泄漏。刚部署应用时,大约有240个连接,但在接下来的30小时里,这个数字逐渐增加到500,这时PostgreSQL就会开始断开连接。
我使用的是SQLAlchemy的线程本地会话:
from sqlalchemy import orm, create_engine
engine = create_engine(os.environ['DATABASE_URL'], echo=False)
Session = orm.scoped_session(orm.sessionmaker(engine))
在Flask网页应用中,.remove()
这个调用是在请求结束时发送给Session
代理对象的:
@app.teardown_request
def teardown_request(exception=None):
if not app.testing:
Session.remove()
这应该和Flask-SQLAlchemy
的做法是一样的。
我还有一些定期任务在循环中运行,每次循环我都会调用.remove()
:
def run_forever():
while True:
do_stuff(Session)
Session.remove()
我到底做错了什么,导致连接泄漏呢?
2 个回答
首先,这种方式来运行后台任务真的很糟糕。可以试试像celery这样的异步调度器。
我不是百分之百确定,所以这只是根据提供的信息做的一个猜测,但我在想每次加载页面时是否都在启动一个新的数据库连接,然后这个连接在监听通知。如果真是这样的话,我在想这个数据库连接是不是从连接池中被移除了,所以在下次加载页面时又重新创建了。
如果真是这样,我的建议是单独设置一个数据库连接专门用来监听通知,这样就不会在队列中活跃了。这个可以在你的工作流程之外进行。
另外
特别是,当同时发起多个请求时,内存泄漏的问题就会出现。同时,我注意到有些请求在执行查询时没有完成就超时了。你可以自己写一些东西来管理这个问题。
如果我没记错的话,之前我在实验SQLAlchemy的时候,发现scoped_session()
这个东西是用来创建可以在多个地方访问的会话的。也就是说,你可以在一个方法里创建一个会话,然后在另一个方法里使用它,而不用手动把会话对象传来传去。
它的工作原理是保持一个会话的列表,并把这些会话和一个“作用域ID”关联起来。默认情况下,它会使用当前线程的ID来获取作用域ID;所以每个线程都有自己的会话。你还可以提供一个scopefunc
,比如说,为每个请求提供一个ID:
# This is (approx.) what flask-sqlalchemy does:
from flask import _request_ctx_stack as context_stack
Session = orm.scoped_session(orm.sessionmaker(engine),
scopefunc=context_stack.__ident_func__)
另外,也要注意其他答案和评论中关于后台任务的内容。