Flask 和 SQLAlchemy 在 PostgreSQL 的事务连接中导致大量 IDLE

Posted

技术标签:

【中文标题】Flask 和 SQLAlchemy 在 PostgreSQL 的事务连接中导致大量 IDLE【英文标题】:Flask and SQLAlchemy causes a lot of IDLE in transaction connections in PostgreSQL 【发布时间】:2015-02-01 00:05:39 【问题描述】:

我有一个使用 SQLAlchemy 访问 PostgreSQL 数据库的 Flask Web 应用程序。

当我启动应用程序时,会立即在 PostgreSQL 中创建一个“事务中”连接。

当应用程序使用了一段时间后,其中几个连接会出现在 pg_stat_activity 中。

一段时间后,某些资源似乎发生了死锁,我必须重新启动应用程序才能使其再次运行。

我已经读到,如果我在关闭数据库会话之前从使用数据库的视图函数返回,就会发生这种情况。所以为了避免这个问题,我创建了如下装饰器:

@app.teardown_appcontext
def shotdown_session(exception=None):
    db.session.remove()

这应该会导致在每次请求后关闭所有会话,并有效避免“事务中”连接的问题。

不幸的是,它似乎没有任何效果。

那么,我该如何真正解决这个问题呢?

更新

我可能应该补充一点,我已经验证了我的装饰器函数确实在运行。 我通过添加打印来验证这一点:

@app.teardown_appcontext
def shotdown_session(exception=None):
    print "@app.teardown_appcontext: shotdown_session()"
    db.session.remove()

我还通过向视图函数添加打印来验证它确实是在视图函数返回之后运行的:

[...]
products = db.session.query(...).all()
print "BEFORE RETURN"
return render_template("show_products.html", products=products)

这会产生如下的日志行:

 * Running on http://0.0.0.0:5000/
 * Restarting with reloader
BEFORE RETURN
@app.teardown_appcontext: shotdown_session()
10.0.0.100 - - [03/Dec/2014 13:41:30] "GET /product/51 HTTP/1.1" 200 -

我还检查了代码并在使用 db.session 的每个函数中的每个返回之前添加了一个 db.session.remove() 调用。 这确实摆脱了 in 事务,但是,它也会导致问题。我将数据库中的 SQLAlchemy 模型对象传递给模板。一些模板然后对这些模型对象执行一些操作,导致应用程序失败,因为对象不再附加到会话。

编辑 2014-12-08

可以在这里看到连接设置:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session
from flask_sqlalchemy import SQLAlchemy

from config import cfg

engine = create_engine(cfg["db_uri"], echo=False, pool_size=10)
db = SQLAlchemy()
Base = db.Model
Session = scoped_session(sessionmaker(bind=engine))

整个应用的结构可以在这里找到:http://paste.yt/p3219.html

【问题讨论】:

您的 sqlalchemy 引擎或连接代码是什么样的?您是否正在使用自动提交或其他类似选项做任何事情? 我在上面添加了 SQL 引擎设置。我还添加了相关文件的摘录,以显示应用程序的整个结构。如果您对我的工作有任何一般意见或改进,请告诉我:) 你是否对 postgres 本身使用了任何池,比如 pgbouncer 之类的?将此类工具与 SQLAlchemy 的池相结合可能会导致此类奇怪的连接问题。 嗨,我没有使用 pgbouncer 或类似的东西。 那我就不知所措了。也许,session.remove() 实际上并没有断开数据库连接,而只是从当前上下文中删除了 SA 会话。连接可能会持续等待新的session。也许您可以在连接对象上显式调用disconnect() 【参考方案1】:

当您在Debug mode 中运行 Flask 时,我已经看到了这种情况。如果您的代码抛出异常并且调试器启动,则事务将永远不会“回滚”或“删除”。因此,用于失败请求的会话永远不会返回到池中。

解决办法是禁用调试模式。

编辑:

在另一种情况下,我看到了这种情况。如果你有自主运行的代码(即不是 HTTP 事务的一部分——就像在启动 Flask 应用程序时启动和产生的独立线程),它通常会涉及睡眠。如果您在睡眠之前访问会话,那么您最终会在睡眠期间出现这样的挂起事务。

另一种可能性是您正在从创建应用程序功能访问会话。如果这样做,请确保 .remove() 它。否则,该会话可能会一直挂在 gevent 应用程序的主线程上。

【讨论】:

我在调试和非调试模式下都得到了这个。 您是否尝试过使用最新的 Flask-SQLAlchemy 模块?看起来您使用的是旧版本。查看代码中的这一点,看看它会自动为您做什么:github.com/mitsuhiko/flask-sqlalchemy/blob/master/… 我会试试然后回来 我刚刚将我的 venv 更新为可通过 pip 获得的最新版本。问题依然存在:( 我也有这个问题。调试错误和最新版本。我认为 dispose 可能会有所帮助。这里给出的答案没有帮助。【参考方案2】:
from sqlalchemy.pool import NullPool

使用 NullPoll 作为 poolclass 为我解决了这个问题。不知道为什么。

编辑(2021 年 3 月 23 日):

尽管我投了反对票,但如果使用 uWSGI,这可能是唯一的选择。查看sqlachemy官方文档

https://docs.sqlalchemy.org/en/14/core/pooling.html#using-connection-pools-with-multiprocessing-or-os-fork

【讨论】:

以上是关于Flask 和 SQLAlchemy 在 PostgreSQL 的事务连接中导致大量 IDLE的主要内容,如果未能解决你的问题,请参考以下文章

Flask 分页的简单用法 / flask_sqlalchemy /无刷新翻转页面(原创)

flask sqlalchemy实现分页功能

flask-sqlalchemy 和sqlalchemy的区别

Flask-SQLAlchemy 和 SQLAlchemy

Flask 学习-19.配置管理flask_sqlalchemy 和 flask_migrate

Python flask-sqlalchemy初级解析