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 и ошибка исчезла.