Python и Django OperationalError (2006, «сервер MySQL ушел»)

Оригинал: Недавно я начал получать MySQL OperationalErrors из некоторого моего старого кода и, похоже, не смог отследить проблему. Поскольку он работал раньше, я подумал, что это может быть обновление программного обеспечения, которое что-то сломало. Я использую python 2.7 с django runfcgi с nginx. Вот мой оригинальный код:

views.py

DBNAME = "test" DBIP = "localhost" DBUSER = "django" DBPASS = "password" db = MySQLdb.connect(DBIP,DBUSER,DBPASS,DBNAME) cursor = db.cursor() def list(request): statement = "SELECT item from table where selected = 1" cursor.execute(statement) results = cursor.fetchall() 

Я пробовал следующее, но он все еще не работает:

views.py

 class DB: conn = None DBNAME = "test" DBIP = "localhost" DBUSER = "django" DBPASS = "password" def connect(self): self.conn = MySQLdb.connect(DBIP,DBUSER,DBPASS,DBNAME) def cursor(self): try: return self.conn.cursor() except (AttributeError, MySQLdb.OperationalError): self.connect() return self.conn.cursor() db = DB() cursor = db.cursor() def list(request): cursor = db.cursor() statement = "SELECT item from table where selected = 1" cursor.execute(statement) results = cursor.fetchall() 

В настоящее время единственным моим MySQLdb.connect() является MySQLdb.connect() в каждой функции, использующей mysql. Также я заметил, что при использовании manage.py runserver меня не было бы этой проблемы, в то время как nginx выбрасывал бы эти ошибки. Я сомневаюсь, что я синхронизируюсь с соединением, потому что list() вызывается в течение нескольких секунд после запуска сервера. Были ли какие-либо обновления программного обеспечения, которое я использую, что может привести к его разрыву / есть ли какое-либо исправление для этого?

Редактирование: я понял, что недавно написал часть среднего уровня для демонстрации функции, и это стало причиной проблемы. Однако я не могу понять, почему. Вот код для среднего предмета

 def process_request_handler(sender, **kwargs): t = threading.Thread(target=dispatch.execute, args=[kwargs['nodes'],kwargs['callback']], kwargs={}) t.setDaemon(True) t.start() return process_request.connect(process_request_handler) 

Согласно документации MySQL , сообщение об ошибке возникает, когда клиент не может отправить вопрос на сервер, скорее всего, потому что сам сервер закрыл соединение. В наиболее распространенном случае сервер закрывает незанятое соединение после (по умолчанию) 8 часов. Это настраивается на стороне сервера.

Документация MySQL дает ряд других возможных причин, которые, возможно, стоит изучить, если они соответствуют вашей ситуации.

Альтернативой вызову connect() в каждой функции (которая может в конечном итоге бесполезно создавать новые соединения) было бы исследование с использованием метода ping() для объекта соединения; это проверяет соединение с возможностью попытки автоматического повторного подключения. Я изо всех сил пытался найти приличную документацию для метода ping() Интернете, но ответ на этот вопрос мог бы помочь.

Примечание. Автоматическое повторное подключение может быть опасным при обработке транзакций, поскольку, как представляется, повторное подключение вызывает неявный откат (и, как представляется, главная причина, почему автосоединение не является признаком реализации MySQLdb).

Иногда, если вы видите «OperationalError: (2006,« сервер MySQL ушел »), это связано с тем, что вы выдаете слишком большой запрос. Это может произойти, например, если вы храните свои сессии в MySQL, и вы пытаетесь поставить что-то действительно большое в сеансе. Чтобы устранить проблему, вам необходимо увеличить значение параметра max_allowed_packet в MySQL.

Значение по умолчанию – 1048576.

Поэтому см. Текущее значение по умолчанию, запустите следующий SQL:

 select @@max_allowed_packet; 

Чтобы временно установить новое значение, запустите следующий SQL:

 set global max_allowed_packet=10485760; 

Чтобы устранить проблему более долго, создайте файл /etc/my.cnf, по крайней мере, следующим образом:

 [mysqld] max_allowed_packet = 16M 

После редактирования /etc/my.cnf вам нужно перезапустить MySQL или перезагрузить компьютер, если вы не знаете, как это сделать.

Проверьте, разрешено ли вам создавать объект соединения mysql в одном потоке, а затем использовать его в другом.

Если это запрещено, используйте threading.Local для соединений с потоками:

 class Db(threading.local): """ thread-local db object """ con = None def __init__(self, ...options...): super(Db, self).__init__() self.con = MySQLdb.connect(...options...) db1 = Db(...) def test(): """safe to run from any thread""" cursor = db.con.cursor() cursor.execute(...) 

Сколько лет этот код? У Django были базы данных, определенные в настройках, по крайней мере .96. Единственное, что я могу придумать, это поддержка multi-db, которая немного изменила ситуацию, но даже это было 1.1 или 1.2.

Даже если вам нужна специальная БД для определенных просмотров, я думаю, вам, вероятно, будет лучше определить ее в настройках.

SQLAlchemy теперь имеет отличную рецензию на то, как вы можете использовать pinging для пессимизма в отношении свежести вашего соединения:

http://docs.sqlalchemy.org/en/latest/core/pooling.html#disconnect-handling-pessimistic

Оттуда,

 from sqlalchemy import exc from sqlalchemy import event from sqlalchemy.pool import Pool @event.listens_for(Pool, "checkout") def ping_connection(dbapi_connection, connection_record, connection_proxy): cursor = dbapi_connection.cursor() try: cursor.execute("SELECT 1") except: # optional - dispose the whole pool # instead of invalidating one at a time # connection_proxy._pool.dispose() # raise DisconnectionError - pool will try # connecting again up to three times before raising. raise exc.DisconnectionError() cursor.close() 

И тест, чтобы удостовериться, что это работает:

 from sqlalchemy import create_engine e = create_engine("mysql://scott:[email protected]/test", echo_pool=True) c1 = e.connect() c2 = e.connect() c3 = e.connect() c1.close() c2.close() c3.close() # pool size is now three. print "Restart the server" raw_input() for i in xrange(10): c = e.connect() print c.execute("select 1").fetchall() c.close() 

У меня была эта проблема, и у меня не было возможности изменить мою конфигурацию. Наконец, я понял, что проблема связана с записью 49500 записей в мой цикл 50000 записей, потому что это было то время, когда я снова пытался (после того, как давно попробовал) ударить по моей второй базе данных.

Поэтому я изменил свой код, так что каждые несколько тысяч записей я снова коснулся второй базы данных (со счетом () очень маленькой таблицы) и исправил ее. Без сомнения, «пинг» или некоторые другие способы касания базы данных также будут работать.

На мой взгляд, общей проблемой, связанной с таким предупреждением, является тот факт, что ваше приложение достигло значения wait_timeout MySQL.

У меня была та же проблема с приложением PythonFLASK, которое я разработал, и я решил очень легко.

PS: Я использовал MySQL 5.7.14

 $ grep timeout /etc/mysql/mysql.conf.d/mysqld.cnf # https://support.rackspace.com/how-to/how-to-change-the-mysql-timeout-on-a-server/ # wait = timeout for application session (tdm) # inteactive = timeout for keyboard session (terminal) # 7 days = 604800s / 4 hours = 14400s wait_timeout = 604800 interactive_timeout = 14400 

Одно важное замечание: если вы ищете переменные через пакетный режим MySQL, значения будут отображаться как есть. Но если вы выполните «SHOW VARIABLES LIKE» wait% '; или «SHOW VARIABLES LIKE» interactive% '; ", значение, настроенное для' interactive_timeout ', будет отображаться для обеих переменных, и я не знаю, почему, но факт в том, что значения, настроенные для каждой переменной, равны' / etc /mysql/mysql.conf.d/mysqld.cnf ', будет соблюдаться процессом MySQL.

С уважением!

Эта ошибка таинственна, потому что MySQL не сообщает, почему она отключается, она просто уходит.

Кажется, есть много причин такого рода отключения. Я только что нашел, если строка запроса слишком велика, сервер отключится. Вероятно, это связано с настройкой max_allowed_packets .

Я тоже борюсь с этой проблемой. Мне не нравится идея увеличения тайм-аута на mysqlserver. Автосоединение с CONNECTION_MAX_AGE не работает, как было упомянуто. К сожалению, я закончил с упаковкой каждого метода, который запрашивает базу данных, подобную этой

 def do_db( callback, *arg, **args): try: return callback(*arg, **args) except (OperationalError, InterfaceError) as e: # Connection has gone away, fiter it with message or error code if you could catch another errors connection.close() return callback(*arg, **args) do_db(User.objects.get, id=123) # instead of User.objects.get(id=123) 

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

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