避免在不经常使用的Python / Flask服务器上出现“MySQL服务器已断开连接”的问题,使用SQLAlchemy。

66
如何配置Flask/SQLAlchemy以在没有数据库连接时创建一个新的数据库连接?
我有一个很少访问的Python/Flask服务器,使用SQLAlchemy。 它每隔几天被访问一次,在第一次访问时通常会出现“MySQL服务器已断开连接”的错误。 后续的页面视图都很好,但这样的初始错误看起来不专业。
我想知道正确处理这种情况的方法 - 像“设置非常长的超时时间”这样的建议,在这种情况下可能需要4天时间,似乎不正确。 如何测试缺少数据库连接并在需要时创建一个连接?
7个回答

54

我以前也遇到过这个问题,发现处理的方法是不要保留会话。问题在于您试图保持连接的时间太长了。相反,使用线程本地作用域的会话,就像在__init__.py或您随处导入的实用程序包中这样:

from sqlalchemy.orm import scoped_session, sessionmaker
Session = scoped_session( sessionmaker() )

首先设置引擎和元数据,这样你就可以跳过每次连接/断开时的配置机制。之后,您可以像这样执行数据库工作:

session = Session()
someObject = session.query( someMappedClass ).get( someId )
# use session like normal ...
session.close()

如果你想保留旧对象并且不想保持会话打开,那么你可以使用上述模式,并像这样重复使用旧对象:

session = Session()
someObject = session.merge( someObject )
# more db stuff
session.close()

关键是,你想打开会话、做你要做的工作,然后关闭会话。这样可以很好地避免超时。对于.merge和.add,有许多选项可让你包括已对分离对象进行的更改或从数据库加载新数据。文档非常冗长,但一旦你知道你要找什么,就可能更容易找到。

要实现最终目标并防止MySQL“消失”,需要解决连接池保持连接时间过长并为您检出旧连接的问题。

为了获得一个新的连接,你可以在create_engine调用中设置pool_recycle选项。将这个pool_recycle设置为连接池中两次检查之间的秒数。如果您想创建一个新的连接而不是返回现有的连接。


谢谢你的回答,我已经编辑了我的代码,所以它使用了scoped_session,每个使用数据库的路由都以session = Session()开头,并以session.close()结尾。然而,我仍然遇到同样的问题 - 这可能是其他什么原因吗? - Ollie Glass
2
听起来你可能需要设置一个pool_recycle时间 - 我已经编辑了我的答案。 - underrun
2
为了清晰起见,您需要同时使用答案的两个元素(原始内容和更新内容)来解决此问题。 - cpatrick
这篇文章对我非常有帮助,谢谢。关于 SQLAlchemy 文档中的池回收,还有一个细节... MySQL 的默认 wait_timeout 设置为 28,800,即 8 小时。因此,如果您禁用了 pool_recycle(默认的 SQLAlchemy 行为),每隔 8 小时您将会遇到超时问题:"请注意,特别是 MySQL 将在连接上没有检测到任何活动时自动断开连接,时间为八个小时(尽管这可以通过 MySQLDB 连接本身和服务器配置进行配置)"。 - Ryan

39
我遇到了类似的问题,但对我来说,在每个会话的5分钟到2小时之间,我都会收到“MySQL已经关闭”的错误。
我正在使用Flask-SQLAlchemy,因此它应该关闭空闲连接,但除非连接已空闲了几个小时,否则似乎没有这样做。
最终,我将其缩小到以下Flask-SQLAlchemy设置:
app.config['SQLALCHEMY_POOL_SIZE'] = 100
app.config['SQLALCHEMY_POOL_RECYCLE'] = 280

默认设置分别为10和7200(2小时)。
需要根据您的环境进行调整。例如,我在许多地方读到SQLALCHEMY_POOL_RECYCLE应该设置为3600,但对我来说并没有用。我使用PythonAnywhere进行托管,并且在5分钟(300秒)后会杀死空闲的MySQL连接。因此,将我的值设置为小于300解决了问题。
希望这能帮助其他人,因为我在这个问题上浪费了太多时间。

http://flask-sqlalchemy.pocoo.org/2.1/config/#configuration-keys

更新:2019年10月8日

自SQLAlchemy v2.4起,配置键'SQLALCHEMY_POOL_SIZE''SQLALCHEMY_POOL_RECYCLE'已被弃用,并将在v3.0中移除。请使用'SQLALCHEMY_ENGINE_OPTIONS'来设置相应的值。

app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {'pool_size' : 100, 'pool_recycle' : 280}

3
我们实际上在我们的文档中有关于pool_recycle值的页面,链接在这里:http://help.pythonanywhere.com/pages/UsingSQLAlchemywithMySQL - hwjp
1
这篇文章的更新部分非常有用。谢谢你。你救了我的一天。 - Biarnès Adrien
他们是不是因为将其变成更加隐晦的子选项而“弃用”了它?或者v3是否带有一些实际上使得此功能变得不再必要的功能? - John Targaryen
@JohnTargaryen 可能是因为他们不想每次添加另一个 SQLAlchemy 引擎选项时都要添加 SQLALCHEMY_* 键。这样他们只需拥有 SQLALCHEMY_ENGINE_OPTIONS 并将该字典直接传递给 create_engine - Phistrom

30

2018年回答:在SQLAlchemy v1.2.0+中,您可以使用连接池预测功能来解决“MySQL服务器已经离开”的问题。

连接池预测 - 连接池现在包括一个可选的“预测”功能,它将为每个连接检出测试池化连接的“活性”,在数据库断开连接的情况下透明地回收DBAPI连接。此功能消除了“池回收”标志的需求以及在数据库重新启动后使用汇编连接引发错误的问题。

使用新参数可以对连接进行悲观测试:

engine = create_engine("mysql+pymysql://user:pw@host/db", pool_pre_ping=True)

很遗憾,flask-sqlalchemy目前还不支持此选项。https://github.com/mitsuhiko/flask-sqlalchemy/pull/577 - Masashi M
@MasashiMiyazaki 由于某些愚蠢的原因,他们仍在支持Python 2.6。 - wim
2
从 @MasashiMiyazaki 的链接可以看到 SQLAlchemy 的解决方案:https://github.com/mitsuhiko/flask-sqlalchemy/issues/589#issuecomment-361075700 - robertlayton

6

@wim所描述的悲观方法是:

pool_pre_ping=True

现在,Flask-SQLAlchemy可以使用配置变量来执行此操作-->

SQLALCHEMY_POOL_PRE_PING = True


6

2
如果您不想使用Flask-SQLAlchemy的最新版本,您可以将SQLALCHEMY_POOL_RECYCLE配置值设置为7200。 - Mark Hildreth

2
当我遇到这个错误时,我正在存储一个大小约为1MB的LONGBLOB / LargeBinary图像。我不得不调整MySQL中的max_allowed_packet配置设置。
我使用了mysqld --max-allowed-packet=16M

0
如果您使用连接池,应将recyle设置为小于DB的wait_timeout。 wait_timeout为60。因此,我将recyle设置为40。
from sqlalchemy.pool import Pool
pool.QueuePool(self.get_connection, max_overflow=0,pool_size=40,recycle=50)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接