我一直在与我的 Flask 应用程序中的一个持续错误作斗争:
OperationalError: (_mysql_exceptions.OperationalError) (2006, 'MySQL server has gone away')
我正在使用 mySQL 服务器实例和 Flask-SQLAlchemy 模块。我仔细检查了 mySQL 实例上连接的过期时间和 SQLAlchemy 配置中的重置时间。没有问题,连接池在 mySQL 连接到期之前重置。我得出的结论是,一定是有什么问题导致连接关闭,然后该连接的下一个用户就中断了。
我正在使用生成四个进程的 uWSGI 运行 Flask 应用程序。如果我切换到单个进程,我将无法重现错误。我猜进程是通过共享连接池互相踩踏的。我添加了以下函数,以便在 uWSGI fork 进程时运行。
from uwsgidecorators import postfork
@postfork
def reset_db_connections():
db.engine.dispose()
在启动时工作正常,并且似乎解决了多个请求同时进入时的问题。然而,现在当一个进程被重置时,该进程的下一个请求会爆炸,并出现类似但不相同的 SQL Server has gone away 错误。这是数据库的初始设置代码
def configure_db():
from my_application.models import SomeModel
db.create_all()
db = SQLAlchemy(app, session_options={'expire_on_commit': False})
configure_db()
数据库的典型使用如下所示:
def save(self):
try:
db.session.add(self)
db.session.commit()
except Exception, ex:
app.logger.error("Error saving campaign: %s" % ex)
db.session.rollback()
读取是以下两种类型之一:
user = db.session.query(User).filter(User.email == email).scalar()
user = User.query.filter(User.email == email).scalar()
我的理解是 Flask-SQLAlchemy 使用范围 session ,因此它们应该在多进程环境中提供一些保护。我需要重置 fork 上的连接池吗?我是否还应该在 fork 时检查实时 session ?
更新:
我把 fork 改成了这样:
@postfork
def reset_db_connections():
db.session.close_all()
db.engine.dispose()
db.create_scoped_session()
我仍然收到 OperationalError,但它只在 fork 期间发生并且似乎不会干扰请求。堆栈跟踪不包括 fork ,但不允许我捕捉它。
Traceback (most recent call last):
File "/home/vagrant/env/local/lib/python2.7/site-packages/sqlalchemy/pool.py", line 636, in _finalize_fairy fairy._reset(pool)
File "/home/vagrant/env/local/lib/python2.7/site-packages/sqlalchemy/pool.py", line 774, in _reset self._reset_agent.rollback()
File "/home/vagrant/env/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1563, in rollback self._do_rollback()
File "/home/vagrant/env/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1601, in _do_rollback self.connection._rollback_impl()
File "/home/vagrant/env/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 670, in _rollback_impl self._handle_dbapi_exception(e, None, None, None, None)
File "/home/vagrant/env/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1341, in _handle_dbapi_exception exc_info
File "/home/vagrant/env/local/lib/python2.7/site-packages/sqlalchemy/util/compat.py", line 199, in raise_from_cause reraise(type(exception), exception, tb=exc_tb)
File "/home/vagrant/env/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 668, in _rollback_impl self.engine.dialect.do_rollback(self.connection)
File "/home/vagrant/env/local/lib/python2.7/site-packages/sqlalchemy/dialects/mysql/base.py", line 2519, in do_rollback dbapi_connection.rollback()
最佳答案
你必须对 uwsgi 使用 lazy-apps=true 选项。
查看此答案:uWSGI, Flask, sqlalchemy, and postgres: SSL error: decryption failed or bad record mac
我不会使用 'lazy' 选项,因为它已被弃用
When working with multiple processes with a master process, uwsgi initializes the application in the master process and then copies the application over to each worker process. The problem is if you open a database connection when initializing your application, you then have multiple processes sharing the same connection, which causes the error above.
在某些情况下,例如使用 flask_admin 或在 app/__init__.py
中调用 Base.metadata.create_all()
,您的应用确实已经与数据库建立了连接在导入时。使用 lazy-apps=false
uwsgi 在导入模块后 fork ,因此连接的文件描述符被复制到 child 。使用 lazy-apps=true
uwsgi fork 自身然后进行导入。这样每个子流程都有自己的连接。
uWSGI tries to (ab)use the Copy On Write semantics of the fork() call whenever possible. By default it will fork after having loaded your applications to share as much of their memory as possible. If this behavior is undesirable for some reason, use the lazy-apps option. This will instruct uWSGI to load the applications after each worker’s fork().
关于python - 在多个 uWSGI 进程中使用 Flask-SQLAlchemy,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34252892/