为啥我在 Django 中收到“MySQL 服务器已消失”异常?

Posted

技术标签:

【中文标题】为啥我在 Django 中收到“MySQL 服务器已消失”异常?【英文标题】:Why am I getting the "MySQL server has gone away" exception in Django?为什么我在 Django 中收到“MySQL 服务器已消失”异常? 【发布时间】:2020-05-03 12:49:10 【问题描述】:

我正在使用 Django 2.2.6。

运行我的 django 项目的同一系统也运行着一个后台服务,它在 unix 套接字上侦听请求。在 Django Admin 中,如果用户点击一个按钮,Django 会在 unix 套接字上发送一个请求,而后台服务会做一些事情。

我的后台服务可以完全访问 Django 的 ORM。它从我项目的 models.py 中导入模型,并且可以毫无问题地查询数据库。

问题是,如果我离开我的 django,而我的后台服务在一夜之间运行,登录到 Django Admin,然后点击按钮,我的后台服务会抛出异常:

django.db.utils.OperationalError: (2006, 'mysql server has gone away')

这似乎是因为 MySQL 数据库有一个超时期限,称为wait_timeout。如果与数据库的连接在很长一段时间内未处于活动状态,MySQL 将断开它。不幸的是,Django 没有注意到,并尝试使用它,抛出错误。

幸运的是,对于 settings.py 中定义的每个数据库,Django 都有自己的内置 CONN_MAX_AGE 变量。如果数据库连接早于 CONN_MAX_AGE,它会在请求之前将其关闭并启动一个新连接。

查看我的 MySQL 数据库:

> show session variables where variable_name = "wait_timeout";
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| wait_timeout  | 28800 |
+---------------+-------+

查看我的 Django 的 CONN_MAX_AGE 变量:

# ./manage.py shell
>>> from django.conf import settings
>>> settings.DATABASES['default']['CONN_MAX_AGE']
0

注意:“默认”数据库是我在 settings.py 中定义的唯一数据库

还要注意,我的 MySQL wait_timeout 和 Django 的 CONN_MAX_AGE 都是默认值 - 我没有更改它们。

根据 Django 文档here,CONN_MAX_AGE 值为 0 意味着:

在每次请求结束时关闭数据库连接

如果 django 打算在每次请求后关闭数据库连接,为什么我会遇到这个错误?为什么它没有在我运行完查询后关闭旧连接,并在几小时后执行新查询时启动新连接?

编辑:

现在,我的解决方案是让我的后台服务进行心跳。每小时一次似乎工作正常。心跳只是一个简单的、低资源消耗的 MySQL 命令,如 MyDjangoModel.objects.exists()。只要它执行 MySQL 查询以刷新 MySQL 超时,它就可以工作。这样做确实给我的后台服务增加了一些复杂性,因为在一种情况下,我原本是单线程的后台服务需要一个后台线程,其唯一的工作就是执行心跳。

如果有更简单的解决方案,或者至少解释为什么会发生这种情况,我想听听。

【问题讨论】:

您可以在 MySQL 的配置中应用 log_warnings=2 以在连接因 wait_timeout 到期而终止时记录在错误日志中。我和@John 在一起,如果 django 自动关闭连接,他们应该默默地重新连接以避免麻烦。 您找到解决方案了吗?请注意,根据您运行 Django 的方式,套接字描述符可能会在进程等之间共享,从而导致 mysql 连接出现各种意外。一般来说,这是分叉服务器或脚本中的一个微妙问题。我只是认为您使用附加后台服务的特殊设置可能会导致与此相关的一些问题。 我的解决方案是使用后台心跳线程。我正在寻找的是为什么会发生这种情况的解释,以及避免使用心跳线程的方法,到目前为止,我还没有找到任何一个。 【参考方案1】:

一般来说,连接应该完成它的任务,然后断开连接。让连接“永远”保持活跃并没有什么好处,你似乎有。

正如您所指出的,“超时”似乎破坏了您的连接。 (有很多超时;你发现了其中之一。)

即使您能够增加超时时间,这也不是一个完整的解决方案。可能会发生其他问题,从而导致断开连接。

两种解决方案; 两个都做

连接,执行任务,断开连接。 检查错误,如果“消失”,则重新连接。

【讨论】:

你说的很笼统。据我所知,正如问题中所展示的那样,默认情况下,Django 似乎已经配置为连接、执行任务和断开连接。我的问题是“为什么不呢”?据我所知,Django 没有“连接”和“断开”功能/方法,您应该调用它来连接/断开与数据库的连接。连接到数据库是在首次访问 ORM 对象时透明地发生的,据我所知,断开连接应该由这些超时处理。【参考方案2】:

几天前我遇到了同样的问题,但没有 Django。 就我而言,我一直在运行一个脚本,它会连接到数据库,然后做它的事情,并且只在需要时才插入数据库。有时,脚本需要几天的时间来完成它的工作,正如您所说,MySQL 的默认 wait_timeout 是 28800 秒或 8 小时。

我不确定我是否正确理解了您的架构,但我怀疑可能会发生类似的事情:您启动服务器,它连接到数据库(查询 #1),然后您睡了一晚(超过 8 小时) ,尝试登录(查询#2),瞧,连接已过期。

您可以通过两种简单的方式验证这个理论是否正确:

1) 将 wait_timeout 设置为 86400(24 小时),通过尝试登录进行与从晚上到早上所做的相同检查,您应该能够在没有错误的情况下这样做。

2) 将wait_timeout 设置为一个超小的值,只需几秒钟,然后重复测试 - 它应该在一分钟内崩溃,而不是一夜之间。

更改参数后不要忘记重新启动 MySQL。

我是如何解决问题的(没有 Django):在重试之前使用 tenacity 包中的简单 retry + 重新启动连接。

你怎么能解决它:刚刚找到this Django plugin,它应该可以做到这一点。从未使用过,但可能值得一试。

注意:虽然从 MySQL 中增加 wait_timeout 可以解决问题,但如果我可以通过这样的重试来解决它,我不会去尝试它。巨大的值可能很危险,因为停滞的连接可能会开始建立并导致另一个错误:连接过多。

【讨论】:

问题的根源是“为什么 Django 这样做?”,这使它成为一个特定于 Django 的问题。为什么数据库在空闲数天后断开我的连接,而它的空闲超时时间为 8 小时,这并不神秘。为什么 Django 没有按照它的配置去做,是。通用解决方案显然也是“重新连接并重试”,但由于 Django 透明地处理连接,并且通常没有预期调用的“连接”或“断开”方法,这比使用时更困难直接与 SQL 数据库交互的库。【参考方案3】:

我遇到的问题和你的完全一样。我使用 watchdogs 库实现了一个监控脚本,在“wait_timeout”结束时,会引发 MySQL 错误。

使用“django.db.close_old_connections()”函数尝试了几次后,它仍然不起作用,但我试图在每个定义的时间间隔关闭旧连接,但它不起作用。我将关闭命令更改为仅在调用我的自定义管理命令之前运行(该命令将与 db 交互并用于因 MySQL 错误而崩溃)并且它开始工作。

显然来自this page,发生这种情况的原因是因为“close_old_connection”函数仅链接到HTTP请求信号,因此不会在特定的自定义脚本中触发。 Django 的文档并没有说明这一点,老实说,我的理解方式与您的理解方式相同。

因此,您可以尝试在与 db 交互之前添加关闭旧连接的调用:

from django.db import close_old_connections
close_old_connections()
do_something_with_db()

【讨论】:

以上是关于为啥我在 Django 中收到“MySQL 服务器已消失”异常?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我在 Django 中使用此调用从 Paypal 收到“内部错误”?

为啥我在我的 Django 项目中不断收到此“名称‘模型’未定义”错误?

为啥我在 Django 收到 403 POST 请求错误?

Heroku/Django 部署:为啥我在成功部署和静态收集时收到错误 500?

为啥我在启动一个新的 Django 项目时会收到​​“ImportError: cannot import name find_spec”?

为啥我会收到“不允许主机 '192.168.1.220' 连接到此 MySQL 服务器”? [复制]