如何绕过 Flask-SQLAlchemy 数据库会话超时(避免"MySQL server has gone away")



我在这里多次看到这个问题,答案对我没有帮助。

据我了解,Flask-SQLAlchemy 在 Flask 请求开始时连接到数据库,并在完成后关闭此会话。但是,如果此请求需要很长时间怎么办?

例如,我的应用接受一个 POST 请求,该请求启动需要写入数据库的长时间运行的 Celery 任务。MySQL服务器在一段时间后"消失",使得在任务结束时写回数据库不起作用。

我在线阅读以设置options["pool_pre_ping"] = True以缓解此类问题(使用此处找到的子类化SQLAlchemy解决方法(,但这没有区别。发生同样的错误,几乎似乎没有任何设置实际上似乎没有更改。

我认为解决方案是重新考虑任务如何写入数据库。目前,我正在使用任务请求中的相同session。如果这是错误的,有什么想法吗?

我认为您对重新考虑任务如何写入数据库有正确的想法。

谨慎的做法可能是让 Flask 路由将任务追加到队列。 一旦任务进入队列,Flask 路由就可以返回,因此 Web 请求上下文将被关闭。

然后,您可以在后台运行另一个 python(非烧瓶(程序,该程序不断从此队列中读取,并从中弹出最新任务,然后执行任务并将其保存到数据库中。

这可以使用 也许您已经在使用 Celery 调用异步函数来执行此操作? 这是一篇关于如何创建这样的芹菜任务并将其与烧瓶路由一起使用的博客文章 https://blog.miguelgrinberg.com/post/using-celery-with-flask

您是对的,Flask-SQLAlchemy 会在 Web 请求上下文关闭时断开数据库会话,这在 flask 路由返回时发生。

若要防止 Flask-SQLAlchemy 关闭会话,不应将session从 Flask 路由传递到异步 Celery 函数。相反,您应该在 celery 异步函数中"创建新会话"(调用会话注册表(。

事实上,在 Web 应用程序中使用 SQLAlchemy 中的会话时,这是一个常规原则。您希望避免传递会话对象。SQLAlchemy 通过使用注册表模式创建返回"线程本地"会话对象的会话注册表来提升此主体。这种"线程本地"会话的想法意味着,当您从会话注册表中获取会话时,您可以确保该会话仅在当前线程中使用。由于您最终要创建一个新线程来执行长时间运行的任务,因此您可以依靠会话注册表为您提供一个线程,该线程不会受到 Web 请求关闭的影响,因为 Web 请求正在不同的线程中处理。

默认情况下,Flask-SQLAlchemy 实现此会话注册表。例如,您可能有一个路线和芹菜任务,如下所示:

from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from celery import Celery
app = Flask(__name__)
db = SQLAlchemy(app)
app.config['CELERY_BROKER_URL'] = 'redis://localhost:6379/0'
app.config['CELERY_RESULT_BACKEND'] = 'redis://localhost:6379/0'
celery = Celery(app.name, broker=app.config['CELERY_BROKER_URL'])
celery.conf.update(app.config)

@celery.task
def do_stuff_that_takes_10_minutes(payload):
session = db.session  # Gets a thread local session from the Session Registry that is different from the session obj you used in the route because this function will be executed in its own thread.
# some long running task here
return result
@app.route('/long_running_task', methods=['GET'])
def long_running_task():
session = db.session  # Get a thread local session from the Session Registry that will close once this web request is complete
# use this session here and do stuff
# Call this function, which Celery will spawn a new thread to do
# Don't pass the above session object into this function, we want this
# session obj to stay only within this thread.
do_stuff_that_takes_10_minutes.delay()
return "Executing task, could take 10 minutes or more."

关闭会话,但保留对象。编辑它们。然后使用merge.确保设置expire_on_commitFalse

最新更新