Сервер MySQL ушел - не работает обработка отключения через обработчик событий checkout - PullRequest
8 голосов
/ 27 октября 2011

Обновление 3/4:

Я провел некоторое тестирование и доказал, что использование обработчика событий checkout для проверки отключений работает с Elixir. Начиная думать, что моя проблема как-то связана с вызовом session.commit() из подпроцесса? Обновление: я просто опроверг себя, вызвав session.commit() в подпроцессе, обновленный пример ниже. Я использую многопроцессорный модуль для создания подпроцесса.

Вот код, который показывает, как он должен работать (даже без использования pool_recycle!):

from sqlalchemy import exc
from sqlalchemy import event
from sqlalchemy.pool import Pool
from elixir import *
import multiprocessing as mp

class SubProcess(mp.Process):
    def run(self):
        a3 = TestModel(name="monkey")
        session.commit()

class TestModel(Entity):
    name = Field(String(255))

@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
metadata.bind = create_engine("mysql://foo:bar@localhost/some_db", echo_pool=True)
setup_all(True)

subP = SubProcess()

a1 = TestModel(name='foo')
session.commit()

# pool size is now three.

print "Restart the server"
raw_input()

subP.start()

#a2 = TestModel(name='bar')
#session.commit()

Обновление 2:

Я вынужден найти другое решение, так как после версии 1.2.2 MySQL-python прекращает поддержку параметраconnect. У кого-нибудь есть решение? : \

Обновление 1 (старое решение, не работает для версий MySQL-python> 1.2.2):

Нашел решение: передача connect_args={'reconnect':True} на вызов create_engine решает проблему, автоматически восстанавливает соединение. Кажется, даже не нужен обработчик событий checkout.

Итак, в примере из вопроса:

metadata.bind = create_engine("mysql://foo:bar@localhost/db_name", pool_size=100, pool_recycle=3600, connect_args={'reconnect':True})

Оригинальный вопрос:

Совершенно немного погуглил для этой проблемы и, похоже, не нашел решения, специфичного для Elixir - я пытаюсь использовать пример " Disconnect Handling - Pessimistic " из документации по SQLAlchemy для обрабатывать MySQL отключается. Однако, когда я проверяю это (перезагружая сервер MySQL), перед обработчиком события checkout возникает ошибка «Сервер MySQL ушел».

Вот код, который я использую для инициализации эликсира:

##### Initialize elixir/SQLAlchemy
# Disconnect handling
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):
    logging.debug("***********ping_connection**************")
    cursor = dbapi_connection.cursor()
    try:
        cursor.execute("SELECT 1")
    except:
        logging.debug("######## DISCONNECTION ERROR #########")            
        # 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()

metadata.bind= create_engine("mysql://foo:bar@localhost/db_name", pool_size=100, pool_recycle=3600)

setup_all()

Я создаю объекты сущностей эликсира и сохраняю их с помощью session.commit(), во время которого я вижу сообщение "ping_connection", сгенерированное из события, определенного выше. Тем не менее, когда я перезагружаю сервер mysql и снова тестирую его, происходит сбой, когда сервер mysql исчезает перед событием ping-соединения.

Вот трассировка стека, начиная с соответствующих строк:

  File "/usr/local/lib/python2.6/dist-packages/elixir/entity.py", line 1135, in get_by
    return cls.query.filter_by(*args, **kwargs).first()
  File "/usr/local/lib/python2.6/dist-packages/sqlalchemy/orm/query.py", line 1963, in first
    ret = list(self[0:1])
  File "/usr/local/lib/python2.6/dist-packages/sqlalchemy/orm/query.py", line 1857, in __getitem__
    return list(res)
  File "/usr/local/lib/python2.6/dist-packages/sqlalchemy/orm/query.py", line 2032, in __iter__
    return self._execute_and_instances(context)
  File "/usr/local/lib/python2.6/dist-packages/sqlalchemy/orm/query.py", line 2047, in _execute_and_instances
    result = conn.execute(querycontext.statement, self._params)
  File "/usr/local/lib/python2.6/dist-packages/sqlalchemy/engine/base.py", line 1399, in execute
    params)
  File "/usr/local/lib/python2.6/dist-packages/sqlalchemy/engine/base.py", line 1532, in _execute_clauseelement
    compiled_sql, distilled_params
  File "/usr/local/lib/python2.6/dist-packages/sqlalchemy/engine/base.py", line 1640, in _execute_context
    context)
  File "/usr/local/lib/python2.6/dist-packages/sqlalchemy/engine/base.py", line 1633, in _execute_context
    context)
  File "/usr/local/lib/python2.6/dist-packages/sqlalchemy/engine/default.py", line 330, in do_execute
    cursor.execute(statement, parameters)
  File "/usr/lib/pymodules/python2.6/MySQLdb/cursors.py", line 166, in execute
    self.errorhandler(self, exc, value)
  File "/usr/lib/pymodules/python2.6/MySQLdb/connections.py", line 35, in defaulterrorhandler
    raise errorclass, errorvalue
OperationalError: (OperationalError) (2006, 'MySQL server has gone away') 

Ответы [ 4 ]

2 голосов
/ 26 июля 2012

проблема в том, что sqlalchemy дает вам один и тот же сеанс каждый раз, когда вы вызываете фабрику создателя сеанса.Из-за этого может случиться так, что более поздний запрос будет выполнен с гораздо более ранним открытым сеансом, если вы не вызвали session.remove() в сеансе.Необходимость запоминать вызов remove() каждый раз, когда вы запрашиваете сеанс, однако не доставляет удовольствия, и sqlalchemy предоставляет гораздо более простую вещь: контекстные «ограниченные» сеансы.

Чтобы создать сеанс с ограничениями, просто оберните создателя сеанса:

from sqlalchemy.orm import scoped_session, sessionmaker
Session = scoped_session(sessionmaker())

Таким образом, вы получаете контекстно-привязанный сеанс каждый раз, когда вызываете фабрику, то есть sqlalchemy вызывает для вас session.remove(), как только завершается вызов вызывающей функции.Смотрите здесь: sqlalchemy - продолжительность жизни контекстной сессии

2 голосов
/ 02 ноября 2011

Последний обходной путь вызывал session.remove() в начале методов перед манипулированием и загрузкой сущностей эликсира.Что он делает, так это возвращает соединение с пулом, так что при повторном его использовании будет инициировано событие извлечения из пула, и наш обработчик обнаружит разъединение. Из документов SQLAlchemy :

Нет необходимости удалять сеанс в конце запроса - другие параметры включают вызов Session.close (), Session.rollback (),Session.commit () в конце, так что существующий сеанс возвращает свои соединения в пул и удаляет любой существующий транзакционный контекст.Ничего не делать - это тоже вариант, если отдельные методы контроллера берут на себя ответственность за то, чтобы ни одна транзакция не оставалась открытой после завершения запроса.,Но тогда я предполагаю, что это предполагает предварительные знания SQLAlchemy?

0 голосов
/ 31 октября 2011

Я не уверен, что это та же проблема, что и у меня, но здесь идет речь:

Когда я столкнулся с MySQL server has gone away, я решил это с помощью create_engine(..., pool_recycle=3600), см. http://www.sqlalchemy.org/docs/dialects/mysql.html#connection-timeouts

0 голосов
/ 27 октября 2011

Используете ли вы один и тот же сеанс для обеих операций (до и после перезапуска mysqld)? Если это так, событие "checkout" происходит только при запуске новой транзакции. Когда вы звоните commit(), новая транзакция запускается (если вы не используете режим автоматической фиксации), и соединение проверяется. Таким образом, вы перезапускаете mysqld после проверки.

Простой хак с вызовом commit() или rollback() непосредственно перед второй операцией (и после перезапуска mysqld) должен решить вашу проблему. В противном случае рассмотрите возможность использования нового нового сеанса каждый раз, когда вы долго ждете после предыдущего коммита.

...