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

Original: недавно я начал получать 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() в каждой функции, которая использует MySQL. Также я заметил, что при использовании , у меня не было бы этой проблемы, в то время как nginx выбросит эти ошибки. Я сомневаюсь, что я тайм-аут с подключением, потому что list() вызывается в течение нескольких секунд после запуска сервера. Были ли какие-либо обновления программного обеспечения, которое я использую, что приведет к этому нарушению/есть ли какие-либо исправления за это?

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

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)

10 ответов


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

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

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

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


иногда, если вы видите " OperationalError: (2006, 'MySQL server ушел')", это потому, что вы выдаете запрос, который слишком велик. Это может произойти, например, если вы храните свои сеансы в 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 connection в одном потоке, а затем использовать его в другом.

если это запрещено, используйте threading.Локальный для соединений в потоке:

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(...)

Я тоже боролся с этой проблемой. Мне не нравится идея увеличения таймаута на 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)

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


сколько лет этому коду? Джанго имеет баз данных, определенной в настройках как минимум .96. Единственное, о чем я могу думать, это поддержка нескольких БД, которая немного изменила ситуацию, но даже это было 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:tiger@localhost/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-record, потому что это было примерно в то время, когда я снова пытался (после того, как попытался давно) попасть в мою вторую базу данных.

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


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

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

P. S: Я использовал 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, значения будут отображаться как есть. Но если вы выполняете " показать переменные, такие как "подождите%"; " или "показать переменные, такие как" interactive%";", значение, настроенное для "interactive_timeout", появится для обеих переменных, и я не знаю, почему, но факт в том, что значения, настроенные для каждой переменной в " /etc/mysql/mysql.conf.д/тузды.cnf', будет уважаться процессом MySQL.

С уважением!


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

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


во-первых, вы должны убедиться, что MySQL session & global enviroments wait_timeout и interactive_timeout значения. И во-вторых, ваш клиент должен попытаться подключиться к серверу ниже этих значений окружения.