为什么在运行一个电报机器人几个小时后,我会得到“MySQL服务器消失了”?

2024-03-28 11:03:04 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在构建一个Django(3.0.5版)应用程序,它使用mysqlclient(2.0.3版)作为数据库后端。此外,我还编写了一个Django命令,它运行一个使用python-telegram-botAPI编写的bot,因此这个bot的任务是无限期地运行,因为它必须随时响应命令

问题是大约24小时。在运行bot(不一定总是空闲)之后,在运行任何命令之后,我会得到一个django.db.utils.OperationalError: (2006, 'MySQL server has gone away')异常

我绝对肯定MySQL服务器一直在运行,并且在我遇到这个异常时仍然在运行。MySQL服务器版本为5.7.35

我的假设是,一些MySQL线程会老化并关闭,因此在重用它们之后,它们不会得到更新

有没有人遇到过这种情况,知道如何解决

Traceback (most recent call last):
  File "/opt/django/gip/venv/lib/python3.6/site-packages/telegram/ext/dispatcher.py", line 555, in process_update
    handler.handle_update(update, self, check, context)
  File "/opt/django/gip/venv/lib/python3.6/site-packages/telegram/ext/handler.py", line 198, in handle_update
    return self.callback(update, context)
  File "/opt/django/gip/gip/hospital/gipcrbot.py", line 114, in ayuda
    perfil = get_permiso_efectivo(update.message.from_user.id)
  File "/opt/django/gip/gip/hospital/telegram/funciones.py", line 33, in get_permiso_efectivo
    u = Telegram.objects.get(idtelegram=userid)
  File "/opt/django/gip/venv/lib/python3.6/site-packages/django/db/models/manager.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/opt/django/gip/venv/lib/python3.6/site-packages/django/db/models/query.py", line 411, in get
    num = len(clone)
  File "/opt/django/gip/venv/lib/python3.6/site-packages/django/db/models/query.py", line 258, in __len__
    self._fetch_all()
  File "/opt/django/gip/venv/lib/python3.6/site-packages/django/db/models/query.py", line 1261, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
  File "/opt/django/gip/venv/lib/python3.6/site-packages/django/db/models/query.py", line 57, in __iter__
    results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
  File "/opt/django/gip/venv/lib/python3.6/site-packages/django/db/models/sql/compiler.py", line 1151, in execute_sql
    cursor.execute(sql, params)
  File "/opt/django/gip/venv/lib/python3.6/site-packages/django/db/backends/utils.py", line 100, in execute
    return super().execute(sql, params)
  File "/opt/django/gip/venv/lib/python3.6/site-packages/django/db/backends/utils.py", line 68, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "/opt/django/gip/venv/lib/python3.6/site-packages/django/db/backends/utils.py", line 77, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/opt/django/gip/venv/lib/python3.6/site-packages/django/db/backends/utils.py", line 86, in _execute
    return self.cursor.execute(sql, params)
  File "/opt/django/gip/venv/lib/python3.6/site-packages/django/db/utils.py", line 90, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/opt/django/gip/venv/lib/python3.6/site-packages/django/db/backends/utils.py", line 86, in _execute
    return self.cursor.execute(sql, params)
  File "/opt/django/gip/venv/lib/python3.6/site-packages/django/db/backends/mysql/base.py", line 74, in execute
    return self.cursor.execute(query, args)
  File "/opt/django/gip/venv/lib/python3.6/site-packages/MySQLdb/cursors.py", line 206, in execute
    res = self._query(query)
  File "/opt/django/gip/venv/lib/python3.6/site-packages/MySQLdb/cursors.py", line 319, in _query
    db.query(q)
  File "/opt/django/gip/venv/lib/python3.6/site-packages/MySQLdb/connections.py", line 259, in query
    _mysql.connection.query(self, query)
django.db.utils.OperationalError: (2006, 'MySQL server has gone away')

我尝试过的事情

我已经尝试更改Django settings.py文件,因此我为CONN_MAX_AGE设置了一个显式值,并且还为MySQL客户机wait_timeout参数设置了一个值,即CONN_MAX_AGE低于wait_timeout

设置.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'OPTIONS': {
            'read_default_file': '/opt/django/gip/gip/gip/my.cnf',
        },
        'CONN_MAX_AGE': 3600,
    }
}

我的.cnf

[client]
...
wait_timeout = 28800

不幸的是,行为完全相同:我在大约24小时内收到一个异常。在运行bot之后


CONN_MAX_AGE设置为None也不会有任何区别


我按照@r-marolahy的建议安装了mysql-server-has-gone-awaypython包,但它也不会有什么不同。运行了近24小时后,“消失”消息再次显示


我还尝试了关闭旧连接的方法:

from django.db import close_old_connections

try:
    #do your long running operation here
except django.db.utils.OperationalError:
    close_old_connections()
    #do your long running operation here

仍然得到同样的结果


Tags: djangoinpyselfexecutedbvenvlib
1条回答
网友
1楼 · 发布于 2024-03-28 11:03:04

连接将因多种原因中断,而不仅仅是超时。 所以,简单地计划一下它的崩溃

计划A(最稳健的解决方案):

每当运行查询时,请检查错误,并使用代码重新连接并重新运行查询(或事务)

方案B(风险交易):

启用自动重新连接。如果您曾经使用多语句事务,那么这是不好的。在事务的中间自动重新连接会导致一个损坏的数据集(违反了“事务”的语义)。这是因为事务的第一部分是^ {CD1}}(由于断开连接),其余的是^ {CD2}}。p>

方案C(直截了当):

当有消息进来时连接;做好你的工作;然后断开连接。也就是说,大部分时间都是断开连接的。如果您可以有很多客户端连接(但很少做任何事情),那么这种方法是必要的

方案D(不值得考虑):

增加各种超时。当其他问题没有解决(网络故障)时,为什么还要麻烦解决一个问题(低超时)

我的偏好?C很容易,而且几乎总是足够的。A需要更多的代码,但却是“最好的”。C和A都可能更好

网友
2楼 · 发布于 2024-03-28 11:03:04

由于服务器超时,MySQL关闭连接时,django发生了此错误。要启用持久连接,请将CONN_MAX_AGE设置为秒的正整数,或将其设置为None以实现无限持久连接(source

更新1: 如果上面建议的解决方案不起作用,您可能需要尝试mysql-server-has-gone-away包。我还没有试过,但在这种情况下可能会有所帮助

Update2:另一种尝试是尝试使用try/except语句捕获此OperationalError并重置与close_old_connections的连接

from django.db import close_old_connections

try:
    #do your long running operation here
except django.db.utils.OperationalError:
    close_old_connections()
    #do your long running operation here

更新3:如所述here

The Django ORM is a synchronous piece of code, and so if you want to access it from asynchronous code you need to do special handling to make sure its connections are closed properly.

但是,Django ORM使用asgiref.sync.sync_to_async适配器,它只能在MySQL关闭连接之前工作。在这种情况下,使用channels.db.database_sync_to_async(即退出时清理旧数据库连接的SyncToAsync版本)可能会解决此问题

您可以像下面那样使用它(source):

from channels.db import database_sync_to_async

async def connect(self):
    self.username = await database_sync_to_async(self.get_name)()

def get_name(self):
    return User.objects.all()[0].name

或者将其用作装饰:

@database_sync_to_async
def get_name(self):
    return User.objects.all()[0].name

确保首先遵循安装说明here

网友
3楼 · 发布于 2024-03-28 11:03:04

这通常是因为服务器端wait_timeout。服务器在等待超时秒后关闭连接。 您应该增加超时时间:

SET SESSION wait_timeout = ...

或者处理此类错误,重新连接并在发生错误时重试

另一个选项是定期(wait_timeout - 1)使用查询(如select 1)ping服务器

相关问题 更多 >