【问题标题】:Does this thread-local Flask-SQLAchemy session cause a "MySQL server has gone away" error?此线程本地 Flask-SQLAlchemy 会话是否会导致“MySQL 服务器已消失”错误?
【发布时间】:2016-01-08 19:25:01
【问题描述】:

我有一个运行独立于用户会话的长时间作业的 Web 应用程序。为了实现这一点,我有一个线程本地 Flask-SQLAlchemy 会话的实现。问题是一天几次,当我访问我的网站时收到MySQL server has gone away 错误。该站点始终在刷新时加载。我认为这个问题与这些线程本地会话有关,但我不确定。

这是我的线程本地会话范围的实现:

@contextmanager
def thread_local_session_scope():
    """Provides a transactional scope around a series of operations.
    Context is local to current thread.
    """
    # See this StackOverflow answer for details:
    # http://stackoverflow.com/a/18265238/1830334
    Session = scoped_session(session_factory)
    threaded_session = Session()
    try:
        yield threaded_session
        threaded_session.commit()
    except:
        threaded_session.rollback()
        raise
    finally:
        Session.remove()

这是我的标准 Flask-SQLAlchemy 会话:

@contextmanager
def session_scope():
    """Provides a transactional scope around a series of operations.
    Context is HTTP request thread using Flask-SQLAlchemy.
    """
    try:
        yield db.session
        db.session.commit()
    except Exception as e:
        print 'Rolling back database'
        print e
        db.session.rollback()
    # Flask-SQLAlchemy handles closing the session after the HTTP request.

然后我像这样使用两个会话上下文管理器:

def build_report(tag):
    report = _save_report(Report())
    thread = Thread(target=_build_report, args=(report.id,))
    thread.daemon = True
    thread.start()
    return report.id

# This executes in the main thread.
def _save_report(report):
    with session_scope() as session:
        session.add(report)
        session.commit()
        return report

# These executes in a separate thread.
def _build_report(report_id):
    with thread_local_session_scope() as session:
        report = do_some_stuff(report_id)
        session.merge(report)

编辑:引擎配置

app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://<username>:<password>@<server>:3306/<db>?charset=utf8'
app.config['SQLALCHEMY_POOL_RECYCLE'] = 3600
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

【问题讨论】:

  • 你也可以发布引擎配置
  • @Busturdust,我已经添加了它们。我什么都不做。 Flask-SQLAlchemy 处理其他所有事情。
  • 就我而言,范围会话、pool_recycle 和 teardown_request 都是必需的。到目前为止,我似乎在开发中运行稳定。对于 Flask-SQLAlchemy,你可能不需要拆解,但我不能确定
  • 我的理解是 Flask-SQLAlchemy 提供了一个作用域会话,其本地上下文是响应 HTTP 请求的线程——并且该会话为您打开和关闭。我会做更多的阅读,看看是不是这样。

标签: python mysql sqlalchemy


【解决方案1】:

尝试添加一个

app.teardown_request(Exception=None)

装饰器,在每个请求结束时执行。我目前遇到了类似的问题,好像今天我已经用它解决了。

@app.teardown_request
def teardown_request(exception=None):
    Session.remove()
    if exception and Session.is_active:
        print(exception)
        Session.rollback()

我不使用Flask-SQLAlchemy,只使用Raw SQLAlchemy,所以它可能对您有所不同。

来自文档

拆解回调是特殊的回调,因为它们被执行 在不同的点。严格来说,它们独立于 实际的请求处理,因为它们绑定到 请求上下文对象。当请求上下文被弹出时, 调用了 teardown_request() 函数。

就我而言,我为每个请求打开一个新的scoped_session,要求我在每个请求结束时将其删除(Flask-SQLAlchemy 可能不需要这个)。此外,如果在上下文中发生,则 teardown_request 函数会传递一个Exception。在这种情况下,如果发生异常(可能导致事务没有被删除,或者需要回滚),我们检查是否有异常,并回滚。

如果这对我自己的测试不起作用,我接下来要做的就是在每次拆解时使用session.commit(),以确保一切正常


更新:似乎 MySQL 在 8 小时后使连接无效,导致 Session 损坏。

在您的引擎配置上设置pool_recycle=3600,或设置为

【讨论】:

  • 不幸的是,由于我的问题的性质不一致以及重新创建所需的时间,我仍在测试这个-_-
  • 我会对此进行测试(如果可行,并在几天内接受您的回答),但同时您能解释一下您认为这样做有什么作用吗?
  • @gwg 已编辑答案。我希望它对你有用,我自己正处于这个烦人的问题之中!
  • 在过去的几周里,我尝试了很多东西,但我认为是什么将pool_recycle 设置为小于 MySQL 的超时时间。我曾假设我们服务器的超时时间是默认值(8 小时),但当我最终检查时它是 10 分钟。我把pool_recycle设置为8分钟,用了4天就没事了。
  • @gwg,非常感谢。我相信你在谈论 mysql 服务器上的wait_timeout。在我的部署环境中是 300。顺便说一句,我也不使用 flask-sqlalchemy。我使用flask-sqlalchemy-session。
猜你喜欢
  • 2021-02-09
  • 1970-01-01
  • 2013-04-26
  • 2020-04-30
  • 2015-03-14
  • 2020-04-12
  • 2013-11-28
  • 2018-08-27
  • 1970-01-01
相关资源
最近更新 更多