OperationalError: соединение MySQL недоступно

Я использую Flask-SQLAlchemy 1.0, Flask 0.10, SQLAlchemy 0.8.2 и Python 2.7.5. Я подключаюсь к MySQL 5.6 с помощью Oracle MySQL Connector/Python 1.0.12.

Когда я перезапускаю свой веб-сервер (либо Apache2, либо Flask), я получаю исключение OperationalError: MySQL Connection not available после истечения MySQL wait_timeout (по умолчанию 8 часов).

Я нашел людей с схожие проблемы и явно установил SQLALCHEMY_POOL_RECYCLE = 7200, хотя По умолчанию флажок-SQLAlchemy. Когда я помещаю точку останова здесь, я вижу, что функция teardown успешно вызывает session.remove() после каждого запроса. Любые идеи?

Обновление 7/21/2014:

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

Во-первых:

@contextmanager
def safe_commit():
    try:
        yield
        db.session.commit()
    except:
        db.session.rollback()
        raise

Это позволило мне обернуть мои вызовы фиксации следующим образом:

with safe_commit():
    model = Model(prop=value)
    db.session.add(model)

Я на 99% уверен, что я не пропустил никаких вызовов db.session.commit с этим методом, и у меня все еще были проблемы.

Второе:

def managed_session():
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            try:
                response = f(*args, **kwargs)
                db.session.commit()
                return response
            except:
                db.session.rollback()
                raise
            finally:
                db.session.close()
        return decorated_function
    return decorator

Чтобы еще больше убедиться, что я не пропускал никаких вызовов фиксации, я создал флеш-упаковку, которая включала код, например (если я правильно помню):

@managed_session()
def hello(self):
    model = Model(prop=value)
    db.session.add(model)

    return render_template(...

К сожалению, ни один из методов не работал. Я также помню, как пыталась вызывать вызовы SELECT (1) в попытке восстановить соединение, но у меня больше нет этого кода.

Для меня, в сущности, MySQL/SQL имеет проблемы с Alchemy. Когда я перебрался в Postgres, мне не пришлось беспокоиться о моих намерениях. Все просто сработало.

Ответ 1

У меня была эта проблема, и это сводило меня с ума. Я пробовал играть с SQLALCHEMY_POOL_RECYCLE, но это, похоже, не устранило проблему.

Наконец-то я нашел http://docs.sqlalchemy.org/en/latest/orm/session.html#when-do-i-construct-a-session-when-do-i-commit-it-and-when-do-i-close-it и адаптирован для фляжки-sqlalchemy.

После того, как я начал использовать следующий шаблон, я не видел проблемы. Кажется, что ключ всегда гарантирует, что выполняется commit() или rollback(). Поэтому, если есть if-then-else, который не протекает через commit() (например, для обнаруженной ошибки), также выполняйте commit() или rollback() перед перенаправлением, прерыванием, вызовом render_template.

class DoSomething(MethodView):
    def get(self):
        try:
            # do stuff
            db.session.commit()
            return flask.render_template('sometemplate.html')
        except:
            db.session.rollback()
            raise
app.add_url_rule('/someurl',view_func=DoSomething.as_view('dosomething'),methods=['GET'])

ОБНОВЛЕНИЕ 7/22/2014

Я обнаружил, что мне также пришлось изменить SQLALCHEMY_POOL_RECYCLE меньше, чем MySQL interactive_timeout. На сервере godaddy interactive_timeout было установлено значение 60, поэтому я устанавливаю значение SQLALCHEMY_POOL_RECYCLE равным 50. Я думаю, что оба шаблона, которые я использовал, и этот тайм-аут были необходимы, чтобы проблема исчезла, но на данный момент я не уверен. Тем не менее, я уверен, что когда SQLALCHEMY_POOL_RECYCLE больше, чем interactive_timeout, я все еще получал операционную ошибку.

Ответ 2

sqlalchemy предоставляет два способа обработки отключений, подробности в документации

Краткая версия:

  • Оптимистично

используйте try...except блок для исключения исключений отключения. Это приведет к возврату 500 к неудачному запросу, тогда веб-приложение будет продолжаться как обычно. Поэтому используйте это, если разъединение происходит нечасто. Примечание. Вам нужно будет обернуть все операции с потенциальным отказом в блоке try...except.

  • Пессимистично (тот, который я использую)

В основном выполняйте дополнительную операцию ping (что-то вроде SELECT 1) каждый раз, когда соединение удаляется из пула. Если ping не удается поднять DisconnectionError, после чего пул хоста попытается создать новое соединение (фактически пул попробует 3 раза, прежде чем официально отказаться). Таким образом, ваше приложение не увидит ошибку 500. Компромисс - это дополнительный SQL-код, хотя, согласно документу, накладные расходы небольшие.

Ответ 3

Недавно я столкнулся с той же проблемой - первый запрос в базу данных MYSQL после длительного периода бездействия приложения FLASK и SQLAlchemy (не менее 8 часов) приводит к необработанному исключению, что, в свою очередь, подразумевает 500 Internal Server Error: Connection Недоступен. Все последующие запросы в порядке.

Мне удалось свернуть проблему до подключения MYSQL, уменьшив значение @@session.wait_timeout (и @@global на всякий случай) до 5 секунд. Тогда каждый нечетный запрос был в порядке, в то время как каждая секунда после 5-секундной паузы терпела неудачу. Вывод был очевиден - SQLAlchemy использовал открытый, но тайм-аут на конечном конце базы данных.

Решение

В моем случае оказалось, что решение изложено в SQLAlchemy - MYSQL ушел в блоге:

Первое, что нужно сделать, это [...] значение pool_recycle должно быть меньше значения MYSQLs wait_timeout.

В документации MYSQL вы можете найти wait_timeout по умолчанию 8 часов (28 800 секунд), а SQLAlchemy engine pool_recycle значение по умолчанию равно -1, что не требует никакого соединения. Я просто передал значение 21 600 (6 часов) функции create_engine и ошибка исчезла.