数据库连接泄漏:PostgreSQL、SQLAlchemy、Flask

3 投票
2 回答
3460 浏览
提问于 2025-04-19 09:19

我在使用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 个回答

0

首先,这种方式来运行后台任务真的很糟糕。可以试试像celery这样的异步调度器。

我不是百分之百确定,所以这只是根据提供的信息做的一个猜测,但我在想每次加载页面时是否都在启动一个新的数据库连接,然后这个连接在监听通知。如果真是这样的话,我在想这个数据库连接是不是从连接池中被移除了,所以在下次加载页面时又重新创建了。

如果真是这样,我的建议是单独设置一个数据库连接专门用来监听通知,这样就不会在队列中活跃了。这个可以在你的工作流程之外进行。

另外

特别是,当同时发起多个请求时,内存泄漏的问题就会出现。同时,我注意到有些请求在执行查询时没有完成就超时了。你可以自己写一些东西来管理这个问题。

4

如果我没记错的话,之前我在实验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__)

另外,也要注意其他答案和评论中关于后台任务的内容。

撰写回答