这个线程局部的Flask-SQLAchemy会话导致"MySQL服务器已经消失"错误吗?

gwg*_*gwg 5 python mysql sqlalchemy

我有一个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()
Run Code Online (Sandbox Code Playgroud)

这是我的标准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.
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

编辑:引擎配置

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
Run Code Online (Sandbox Code Playgroud)

Bus*_*ust 2

尝试添加一个

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()
Run Code Online (Sandbox Code Playgroud)

我不使用Flask-SQLAlchemyOnly Raw SQLAlchemy,所以它对你来说可能会有所不同。

来自文档

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

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

如果这对我自己的测试不起作用,那么我接下来要做的就是session.commit()每次拆卸时,只是为了确保一切都冲洗干净


更新:MySQL 似乎也会在 8 小时后使连接失效,导致会话损坏。

设置pool_recycle=3600您的引擎配置,或设置 < MySQL 超时。这与适当的会话范围(关闭会话)相结合应该可以做到。

  • 在过去的几周里,我尝试了很多事情,但我认为它的作用是将“pool_recycle”设置为小于 MySQL 的超时时间。我假设我们服务器的超时是默认的(8 小时),但当我最终检查时,它是 10 分钟。我把`pool_recycle`设置为8分钟,4天了都没问题。 (2认同)