【问题标题】:Django, after upgrade: MySQL server has gone awayDjango,升级后:MySQL 服务器已经消失
【发布时间】:2015-01-13 12:47:07
【问题描述】:

我最近从 Django 1.4 升级到 Django 1.7,并且由于某些脚本不断收到以下错误消息,有时:

OperationalError: (2006, 'MySQL server has gone away')

脚本是非常长或连续运行的任务,可能涉及几分钟不与数据库通信的阶段,因此连接超时。但是,在我升级之前,这没问题,因为 Django 似乎会自动重新建立连接。现在它没有,这意味着任务经常在中间停止并失败。

有谁知道发生了什么变化以及如何修复它?

是否可能与该票/修复有关:https://code.djangoproject.com/ticket/21463

非常感谢!

【问题讨论】:

标签: mysql django timeout


【解决方案1】:

我知道这是一个老问题,但我的情况的解决方案不在任何当前答案中。

我的问题的根源在于服务器端。通过检查服务器超时设置(使用show global variables like '%timeout'),我发现wait_timeout 变量设置为120 秒,因此任何需要这么长时间的任务都会在我尝试检索时抛出“服务器已消失”异常或保存数据。添加CONN_MAX_AGE 没有任何区别。

解决方案 #1 - 更改服务器的 wait_timeout 设置(如果您有权这样做)

在您的服务器中使用set global wait_timeout=1*60*60(时间以秒为单位)。

解决方案 #2 - 在数据库空闲时间后强制建立新连接

在您的 Django 视图/任务中:

from django.db import connection
from django.db.utils import OperationalError

cursor = connection.cursor()
cursor.execute('SELECT 1')  # <-- no issues here

# time consuming code...

try:
    cursor.execute('SELECT 1')
except OperationalError:
    connection.connect()
    cursor = connection.cursor()
    cursor.execute('SELECT 1')

观察:
这也应该适用于 ORM 请求
Django 版本 3.1.6

【讨论】:

    【解决方案2】:

    SHORT:

    1. pip install mysql_server_has_gone_away

    2. settings.py:

    DATABASES = {
        'default': {
            'ENGINE': 'mysql_server_has_gone_away'
        }
    }
    

    TL;DR

    在我的情况下,我使用的是 django ORM 和 long living request (webocket)。 CONN_MAX_AGE 在我的情况下不起作用。

    一开始我创建了一个包装器来尝试捕获错误,但是由于 django 延迟加载,它只是不清楚你应该为哪些东西包装它。所以我最终在整个项目中复制了这段代码,这让我很头疼。而不是写例如User.objects.get(id=3) 我会做do_db(User.objects.get, id=3),而数据库是try: return callback(*args, **kwargs); catch e: conn.close(); callback(*args, **kwargs)。

    在深入研究 django 后端时,我们可以在连接级别迁移此解决方案。因此,进入 db 的每个查询都将被包裹起来:

    settings.py:

    DATABASES = {
        'default': {
            'ENGINE': 'lol'
        }
    }
    

    lol/base.py:

    """ 
    https://stackoverflow.com/a/60894948/3872976
    """
    
    from django.db.backends.mysql import base
    
    
    def check_mysql_gone_away(db_wrapper):
        def decorate(f):
            def wrapper(self, query, args=None):
                try:
                    return f(self, query, args)
                except (base.Database.OperationalError, base.Database.InterfaceError) as e:
                    if 'MySQL server has gone away' in str(e):
                        db_wrapper.connection.close()
                        db_wrapper.connect()
                        self.cursor = db_wrapper.connection.cursor()
                        return f(self, query, args)
                    # Map some error codes to IntegrityError, since they seem to be
                    # misclassified and Django would prefer the more logical place.
                    if e.args[0] in self.codes_for_integrityerror:
                        raise base.utils.IntegrityError(*tuple(e.args))
                    raise
            return wrapper
    
        return decorate
    
    
    class DatabaseWrapper(base.DatabaseWrapper):
    
        def create_cursor(self, name=None):
    
            class CursorWrapper(base.CursorWrapper):
    
                @check_mysql_gone_away(self)
                def execute(self, query, args=None):
                    return self.cursor.execute(query, args)
    
                @check_mysql_gone_away(self)
                def executemany(self, query, args):
                    return self.cursor.executemany(query, args)
    
            cursor = self.connection.cursor()
            return CursorWrapper(cursor)
    

    您应该注意,如果您在原子操作期间 mysql 断开连接,您将遇到事务问题。但不幸的是,没有其他办法。

    【讨论】:

    • 你如何与 Django 沟通,lol/base.py 是实现“lol”引擎的文件?
    • 检查 db_wrapper.in_atomic_block 可能是个好主意,如果它是真的不要重试查询。
    【解决方案3】:

    我遇到了同样的问题,我的解决方案在哪里。我是 Django 的新手,我在这里已经很晚了,但我正在发布我的解决方案。也许它会帮助某人。

    DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'your_dabase',
        'USER': 'your_user',
        'PASSWORD': 'your_password',
        'HOST': 'your_host',
        'PORT': 'your_port',
        'CONN_MAX_AGE': 290,
    },
    'OPTIONS': {
        'timeout':20,
        } 
    

    }

    我添加了 CONN_MAX_AGE 和 OPTIONS,现在它运行良好。

    【讨论】:

      【解决方案4】:

      我们也注意到了这一点。将 CONN_MAX_AGE 设置为低于 MySQL / MariaDB 的 wait_timeout 的上述答案适用于网络。

      但是,对于长时间运行的任务,这似乎不起作用。相反,我们每次执行一个长时间运行的任务时都将其包装并关闭连接。

      我们将其与我们自己的自定义池相结合。接受或离开它 - 默认的 Django 具有零控制 - 这不是我们在生产中喜欢的东西。我们设置了一个最大池以在服务器终止连接过多的数据库之前终止服务器。将其用作任务的装饰器:

      @close_db_connection()
      def task_do_something():
          print 'Hello'
      
      
      '''
      Created on Dec 23, 2017
      
      @author: Kevin
      '''
      from functools import wraps
      
      def close_db_connection(ExceptionToCheck=Exception, raise_exception=False, notify=False):
          """Close the database connection when we're finished, django will have to get a new one..."""
          def deco_wrap(f):
              @wraps(f)
              def f_wrap(*args, **kwargs):
                  try:
                      return f(*args, **kwargs)
                  except Exception as e:
                      raise e
                  finally:
                      from django.db import connection; 
                      connection.close();
      
              return f_wrap
          return deco_wrap
      

      【讨论】:

        【解决方案5】:

        在 Django 1.9 上: 我有一个持续的 Django Shell 在 unix screen 中运行超过 48 小时无人看管。 当我回到它并运行 &lt;some_model&gt;.objects.filter 时,它抛出了 OperationalError: (2006, 'MySQL server has gone away')

        快速的import django.db; django.db.close_old_connections() 为我解决了问题。

        我在Django Docs for 1.9 上找不到close_old_connections() 的文档,但这里是direct link to its implementation in Django Codebase on Github

        【讨论】:

          【解决方案6】:

          这种行为的原因是持久连接到数据库,这是在 Django 1.6 中引入的。

          为了防止连接超时错误,您应该将 settings.py 中的 CONN_MAX_AGE 设置为小于 MySQL 配置中的 wait_timeout (my.cnf) 的值。在这种情况下,Django 检测到连接需要在 MySQL 抛出它之前重新打开。 MySQL 5.7 的默认值为 28800 秒。

          settings.py:

          DATABASES = {
              'default': {
                  'ENGINE': 'django.db.backends.mysql',
                  'CONN_MAX_AGE': 3600,
                  <other params here>
              }
          }
          

          文档:https://docs.djangoproject.com/en/1.7/ref/settings/#conn-max-age

          my.cnf:

          wait_timeout = 28800
          

          文档:https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_wait_timeout

          【讨论】:

          • 非常感谢。我刚刚使用 django cms 3.0.3、django 1.6.5 和 zinnia 更新了一个应用程序,所有这些都使用设置为电子邮件用户名模型的 AUTH_USER_MODEL。做了一次噩梦。终于让所有工作正常了,并且仅来自ngnix的“mysql服务器消失”错误(运行服务器工作正常)。你真是个学者!
          • 虽然奇​​怪的是我不需要在我的任何其他 Django 1.7+ 应用程序上使用 CONN_MAX_AGE 设置,而且我之前已经升级到 1.6-1.7 以上。我只能假设 MySQL 配置中的 wait_timeout 在其他服务器上是不同的。
          • 很高兴听到这对您有帮助)
          • 其实这并没有解决我的问题。仅在使用 uwsgi 和 nginx 时发生。我怀疑这与此有关:serverfault.com/questions/407612/…
          【解决方案7】:

          也许超时对某些人来说是个问题,但我在尝试编写一个非常大的 BLOB 字段时遇到了这个问题。我通过增加 mysql 配置文件中允许的最大数据包大小来解决它......

          max_allowed_pa​​cket=4M

          改成/etc/my.cnf后别忘了重启mysql。此页面帮助...

          http://dev.mysql.com/doc/refman/5.5/en/packet-too-large.html

          【讨论】:

            【解决方案8】:

            我有一个正在运行的后台进程rqworker,它执行单独的作业以在某些用户操作后刷新一些数据。

            我总是得到OperationalError: (2006, 'MySQL server has gone away') 如果在超过wait_timeout 秒内没有用户操作。 即使我设置的 CONN_MAX_AGE 小于 MySQL wait_timeout。

            据我了解,如果 Django 在此超时前自动检查并关闭其连接,则更改 CONN_MAX_AGE 可能会有所帮助。但是 Django 1.7.x 只在每个请求之前和之后检查它(参见 django/db/init.py#L101-L112 )。

            正如Django ticket 15119 中所述,我们可以看到 Django 在执行每个查询之前都会进行 ping 以验证连接是否处于活动状态。此行为已在 commit 282b2f4 中修复。

            Django 开发人员在https://code.djangoproject.com/ticket/21597#comment:29 中为所有此类问题提供了一个简短的答案

            因此,我的rqworker 进程必须为每个新作业验证连接本身。 (注意:如果我们关闭一个连接,那么 Django 会创建一个新的)。

            我将使用 Django 按请求方法处理作业,并在每个作业之前和之后调用 django.db.close_old_connections()。是的,CONN_MAX_AGE 应该小于 MySQL wait_timeout,因为 Django 不会在 django.db.close_old_connections() 方法中检查 MySQL server has gone away。

            【讨论】:

              【解决方案9】:

              在 django 1.6 中,当(mysql 的)wait_timeout 过去时,DB 访问会导致 (2006, 'MySQL server has gone away') 错误。 在 django 1.5.1 中并非如此

              我在使用运行 django 代码的工作人员(使用 gearman)时注意到了这个错误。

              复制:

              通过编辑 /etc/mysql/my.cnf 将超时设置为低值 在 [mysqld] 下添加以下内容

              wait_timeout = 10
              interactive_timeout = 10
              

              然后

              % python manage.py shell

              >>> # access DB 
              >>> import django.contrib.auth.models
              >>> print list(django.contrib.auth.models.User.objects.all())
              >>> import time
              >>> time.sleep(15)
              >>> print list(django.contrib.auth.models.User.objects.all())
              

              现在你得到了错误。

              我在网上找到的简单解决方案是在访问之前调用django.db.close_connection()

              >>> import django.db
              >>> django.db.close_connection()
              >>> print list(django.contrib.auth.models.User.objects.all())
              

              工作正常。

              【讨论】:

              • 这是我喜欢的解决方案,但新版本中不存在 django.db.close_connection
              • 对我来说这是最好的答案。我在较新的 Django 版本中使用db.connections['default'].close()。
              猜你喜欢
              • 2017-04-02
              • 2019-01-11
              • 2011-06-07
              • 2010-12-20
              • 2015-01-15
              • 2016-03-06
              • 2011-11-09
              • 2014-02-12
              相关资源
              最近更新 更多