Почему я получаю исключение "MySQL сервер ушел" в Django? - PullRequest
4 голосов
/ 16 января 2020

Я работаю с Django 2.2.6.

В той же системе, в которой работает мой проект django, также работает фоновая служба, прослушивающая сокет unix для запросов. В Django Admin, если пользователь нажимает кнопку, Django отправляет запрос на сокет unix, и фоновая служба что-то делает.

Моя фоновая служба имеет полный доступ к Django ' с ОРМ. Он импортирует модели из файла models.py моего проекта и может без проблем запрашивать базу данных.

Проблема заключается в том, что если я оставлю свой django и мою фоновую службу запущенной на ночь, войдите в систему Django Admin и, нажав кнопку, моя фоновая служба выдает исключение:

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

Похоже, это потому, что база данных MySQL имеет период ожидания, называемый wait_timeout. Если соединение с базой данных не активно в течение этого времени, MySQL отключит его. К сожалению, Django не замечает и пытается использовать его, выдавая ошибку.

К счастью, Django имеет собственную встроенную переменную CONN_MAX_AGE для каждой базы данных, определенной в settings.py. Если подключение к базе данных старше, чем 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 документам здесь , значение CONN_MAX_AGE 0 означает:

закрывать соединения с базой данных в конце каждого запроса

Если django предназначено для закрытия соединения с базой данных после каждого запроса, почему я столкнулся с этой ошибкой? Почему он не закрывает старые соединения, как только я закончу свой запрос и запускаю новое соединение, когда я делаю новый запрос, спустя несколько часов?

Редактировать:

Прямо сейчас, мое решение состоит в том, чтобы фоновая служба делала биение сердца. Кажется, один раз в час работает нормально. Сердцебиение - это простая команда с низким потреблением ресурсов MySQL, такая как MyDjangoModel.objects.exists (). Пока он выполняет запрос MySQL для восстановления sh таймаута MySQL, он работает. Это усложняет мою фоновую службу, так как в одном случае моей однопоточной фоновой службе в противном случае требовался фоновый поток, единственной задачей которого было выполнение тактов.

Если есть более простое решение, или по крайней мере, объяснение того, почему это происходит, я бы хотел услышать это.

Ответы [ 2 ]

0 голосов
/ 25 января 2020

У меня была такая же проблема всего несколько дней go, но без Django. В моем случае я всегда выполнял сценарий, который подключался к БД, затем выполнял свою работу и вставлял БД только при необходимости. Иногда сценарию приходилось выполнять свои функции в течение нескольких дней, и, как вы сказали, по умолчанию wait_timeout из MySQL составляет 28800 секунд или 8 часов.

Я не уверен, что правильно понял вашу архитектуру , но я подозреваю, что может происходить нечто подобное: вы запускаете сервер, он подключается к БД (запрос № 1), затем вы спите одну ночь (более 8 часов), пытаетесь войти (запрос № 2) и вуаля соединение истекло.

Проверить правильность этой теории можно двумя простыми способами:

1) установите wait_timeout на 86400 (24 часа), выполните ту же проверку, что и вы. с вечера до утра, пытаясь войти в систему, и вы сможете делать это без ошибок.

2) установите wait_timeout в очень маленькое значение, всего несколько секунд, и повторите тест - он должен cra sh за минуту, а не за ночь.

Не забудьте перезапустить MySQL после изменения его параметров.

Как я решил проблему (без Django): использование простого retry из соединения tenacity pack + перезапустите перед повторной попыткой.

Как вы могли бы ее решить: только что обнаружил этот Django плагин , который должен делать именно это. Никогда не использовал его, но, возможно, стоит попробовать.

Примечание: Хотя увеличение wait_timeout с MySQL может решить проблему, я бы не стал go, если бы я можно исправить с помощью таких попыток. Огромные значения могут быть опасны, так как заблокированные соединения могут начать накапливаться и привести к другой ошибке: слишком много соединений.

0 голосов
/ 20 января 2020

В общем случае соединение должно выполнить свою задачу, а затем отключиться. Нет хорошего преимущества в том, чтобы поддерживать соединение «навсегда», которое у вас, кажется, есть.

Как вы заметили, кажется, что «тайм-аут» отключил ваше соединение. (Существует много тайм-аутов; вы обнаружили один из них.)

Даже если вы можете увеличить тайм-аут, это не будет полным решением. Могут произойти другие сбои, приводящие к отключению.

Два решения; сделать их оба :

  • Подключиться, выполнить задание, отключиться.
  • Проверить ошибки, если «пропал», затем повторно подключиться.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...