Django, после обновления: сервер MySQL ушел

Недавно я обновился с Django 1.4 до Django 1.7, и поскольку я продолжаю получать следующее сообщение об ошибке для некоторых скриптов, иногда:

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

Сценарии - это очень длинные или постоянно выполняемые задачи, которые могут включать фазы не связывания с db в течение нескольких минут, поэтому время соединения заканчивается. Однако, прежде чем я обновился, это не было проблемой, поскольку Django, казалось, автоматически восстановил соединение. Теперь это не означает, что задачи часто останавливаются и терпят неудачу посередине.

Кто-нибудь знает, что изменилось и как я могу это исправить?

Возможно, это связано с этим билетом/исправлением: https://code.djangoproject.com/ticket/21463

Спасибо большое!

Ответ 1

Причиной такого поведения является постоянное подключение к базе данных, которое было представлено в Django 1.6.

Чтобы предотвратить ошибку времени ожидания соединения, вы должны установить для CONN_MAX_AGE в settings.py значение, которое меньше, чем wait_timeout в конфигурации MySQL (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

Ответ 2

У меня есть работающий фоновый процесс rqworker, который выполняет отдельные задания для обновления некоторых данных после некоторых действий пользователя.

Я всегда получаю OperationalError: (2006, 'MySQL server has gone away') если в течение более чем wait_timeout секунд не было действий пользователя. Даже если я установил CONN_MAX_AGE меньше, чем MySQL wait_timeout.

Как я понимаю, изменение CONN_MAX_AGE может помочь, если Django автоматически проверит и закроет свои подключения этим таймаутом. Но 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 не проверяет, есть ли MySQL server has gone away в django.db.close_old_connections().

Ответ 3

На Джанго 1.9: У меня была запущенная Django Shell на экране Unix, оставленная без присмотра более 48 часов. Когда я вернулся к нему и запустил <some_model>.objects.filter он бросил OperationalError: (2006, 'MySQL server has gone away')

Быстрый import django.db; django.db.close_old_connections() помог мне.

Я не смог найти документацию для close_old_connections() в Django Docs для 1.9, однако здесь есть прямая ссылка на его реализацию в Django Codebase на Github.

Ответ 4

В django 1.6, когда wait_timeout прошло (из mysql), тогда доступ к БД вызвал ошибку (2006, "сервер MySQL ушел" ). Это было не так в django 1.5.1

Я заметил эту ошибку при использовании рабочих, которые запускают код django (используя механизм).

Воспроизведение:

Установите тайм-аут на низкое значение, отредактировав /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())

работает нормально.

Ответ 5

Мы тоже это заметили. Ответ выше установки 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

Ответ 6

Возможно, тайм-аут является проблемой для некоторых, но я столкнулся с этой проблемой при попытке написать очень большое поле BLOB. Я разрешил его, увеличив максимально допустимый размер пакета в файле конфигурации mysql...

max_allowed_packet = 4M

Не забудьте перезапустить mysql после изменения на /etc/my.cnf. Эта страница помогла...

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