Хобрук: Ваш путь к мастерству в программировании

mariadb.DatabaseError после длительного бездействия при подключении к mariadb в Python 3

Итак, я разрабатываю эту онлайн-игру, похожую на telnet, и она не очень популярна (кто знает, когда-нибудь), поэтому соединение с базой данных моего игрового движка не используется часами по ночам. Это один сценарий, который ожидает событий, поэтому он продолжает работать.

При первом выполнении запроса после нескольких часов бездействия я получаю mariadb.DatabaseError при попытке выполнить курсор. Если я повторю запрос, он снова работает. Таким образом, хотя функция выдает исключение о потере соединения, она восстанавливает его.

Мой вопрос: как мне справиться с этим?

Это вещи, которые я вижу в качестве возможных решений, но, на мой взгляд, они не очень хороши:

  • обертывание каждого запроса в структуру try-except, делает код громоздким, в основном ненужным и повторяющимся кодом
  • написание моей собственной функции «декоратора» для выполнения запроса, который затем повторно инициализирует базу данных, когда я получаю mariadb.DatabaseError, что кажется лучше, но заставляет меня писать функции-оболочки вокруг (почти) идеально работающих библиотечных функций
  • выполнение в основном бессмысленного запроса «ping» каждые N минут, что создает нагрузку на базу данных, которая бесполезна в 99,9% случаев.

Вот код для иллюстрации:

import mariadb
class Db:
  ...
  def __init__(self):
    self.conn = mariadb.connect(user=self.__db_user, password=self.__db_pass, host=self.__db_host, port=self.__db_port, database=self.__db_name)

  def one_of_many_functions(self, ...):
    cur = self.conn.cursor()
    cur.execute('SELECT ...') # Here is where the mariadb.DatabaseError happens after long inactivity, and otherwise runs fine
    ...

На самом деле я действительно не понимаю, почему реализация mariadb в python не справляется с этим. Когда соединение потеряно, cur.execute выдаст mariadb.DatabaseError, но никаких действий предпринимать не нужно, потому что, если я повторю запрос с тем же соединением с базой данных, оно снова сработает. Таким образом, соединение восстанавливается само. Почему компонент заставляет меня запрашивать, в то время как он «восстанавливает» само соединение и может снова запросить?

Но поскольку это то, что есть, мой вопрос: как лучше всего справиться с этим?


  • Я действительно не хочу писать блок try-excet вокруг каждого запроса и цикл while вокруг него, чтобы повторить попытку, пока он не сработает... Кроме того, писать для него конкретную функцию глупо из-за ошибки, которую я не могу контролировать . 15.09.2020
  • @matt, почему оставлять соединение с БД открытым для одного приложения, которое время от времени нуждается в нем, нехорошо? Когда я должен закрыть/снова открыть это соединение? После 5 запросов? Через 5 минут? Очень непредсказуемо, как будет использоваться база данных, потому что это сервер обработки запросов для всех, кто входит в игру... Повторное открытие соединения с базой данных для каждого запроса мне тоже кажется неправильным. 15.09.2020
  • Я повторюсь. Это может быть проблема, связанная с тем, что ваша БД будет заполняться открытыми соединениями, когда приложения их не закрывают. 15.09.2020
  • Пользователи намного медленнее, чем БД. В любое время, когда вам нужно дождаться ввода пользователя, уместно открывать/закрывать. Кроме того, вы можете заглянуть в пул соединений. Они могут управлять поддержанием связи. 15.09.2020
  • Извините, @matt, что не так ясно выразился, но я знаю все эти вещи, о которых вы говорите. Я много лет писал программное обеспечение для БД и решал эти проблемы с помощью таких вещей, как функции декоратора. Моя точка зрения заключается в том, что это не должно быть необходимо. Мой сервер также имеет постоянное открытое соединение Redis, где он прослушивает события. Почему соединение с БД должно через некоторое время прерваться и почему оно не должно восстанавливаться автоматически? Я понимаю, что могу заглянуть в свои настройки mariadb, но это нужно помнить каждый раз, когда вы меняете db. Мой вопрос в надежде найти правильное решение для этого. 15.09.2020
  • И я знаю, что могу просто каждый раз создавать новое соединение, и я знаю, что могу создать пул БД. Оба не должны быть необходимы. Во-первых, потому что постоянное соединение идеально подходит для этой ситуации. Последнее потому, что если такая балансировка нагрузки нужна, я буду использовать разделение высокой доступности, запустив дополнительный сервер. Игра основана на этом. 15.09.2020
  • Я нашел причину в stackoverflow.com/questions/51506416/ 15.09.2020
  • Ok. Вы должны понимать, что вопрос перерос от непонимания к пониманию, но к мысли, что это не нормально. Пожалуйста, не обижайтесь. Оно было адресовано не вам. Хотя то, что вы говорите, неправда. Я не спрашивал «что я могу сделать», я просил лучший способ, чем те, которые я считал «недостаточно хорошими», если хотите. 15.09.2020
  • Однако мой вопрос заключался в том, «как мне с этим справиться», и никто не дал мне лучшего способа справиться с этим, чем те, которые я уже дал. Грустно, что мой вопрос получил -1 из-за этого. 15.09.2020
  • Я изменю это. 15.09.2020

Ответы:


1

Если вы установите значение длительного времени ожидания, нет даже гарантии, что соединение прервется по другим причинам (тайм-аут клиента, 24-часовое отключение, ...)

Можно также установить auto_reconnect, как в следующем примере:

import mariadb

conn1= mariadb.connect()
conn2= mariadb.connect()

# Force MariaDB/Connector Python to reconnect
conn2.auto_reconnect= True

cursor1= conn1.cursor()

print("Connid of connection 2: %s" % conn2.connection_id);

# Since we don't want to wait, we kill the conn2 intentionally
cursor1.execute("KILL %s" % conn2.connection_id)

cursor2= conn2.cursor()
cursor2.execute("select connection_id()")
row= cursor2.fetchall()
print("Connid of connection 2: %s" % conn2.connection_id);
print(row)

Выход:

Connid of connection 2: 174
Connid of connection 2: 175
[(175,)]

Таким образом, после того, как соединение 2 было уничтожено, next cursor.execute установит новое соединение перед выполнением инструкции. Это решение не будет работать, если вы используете существующий открытый курсор, поскольку внутренний дескриптор оператора становится недействительным.

15.09.2020
  • МОЙ БОГ! Я не знал, что это существует! Да, обрыв связи при использовании курсора я не пытаюсь исправить. А вот автоматическое исправление разорванного соединения перед выполнением запроса — это то, что мне нужно. где ты нашел эту информацию? Я не нашел реального API для коннектора mariadb. Вы знаете, где я могу найти его? 15.09.2020
  • Поскольку я являюсь автором/сопровождающим MariaDB Connector/Python, найти его было нетрудно :-) Он также задокументирован по адресу mariadb-corporation.github.io/mariadb-connector-python/ 15.09.2020
  • Ха-ха. Большой! Спасибо! 16.09.2020
  • Хм, похоже, это не работает, потому что сегодня у меня снова возникла ошибка при первом запросе после нескольких часов бездействия. Или я неправильно понимаю? Разве это не должно помешать вам получить ошибку и не должно ли оно автоматически повторить попытку? 17.09.2020

  • 2

    Используете ли вы сокет или TCP/IP для подключения?

    Соединения TCP/IP предназначены для очистки после периода отсутствия трафика. Вы можете сказать, что это идиотизм, но на самом деле нет лучшего способа узнать, падает ли программа.

    По той же причине базы данных имеют собственный механизм тайм-аута. Для MySQL это называется wait_timeout.

    Обычно объект соединения (или его обертка) позаботится о выполнении какого-либо неактивного запроса, если с соединением больше ничего не происходит, что-то вроде select 1. Это стандартная практика. Проверьте документацию по вашему объекту подключения — возможно, он уже есть, вам просто нужно его настроить. Используйте что-то вроде 30-60 секунд.

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

    15.09.2020
  • Да, вы, конечно, правы, и другого я и не ожидал. Но чего я не понимаю, так это того, что mariadb Python не обрабатывает это автоматически. Он переподключается после сбоя (мне это не нужно), поэтому, если вы выполняете запрос, он выдает исключение, и если вы затем снова запрашиваете, он снова работает. Почему реализация не делает это сразу за вас? Вот что я имею в виду под идиотизмом. 15.09.2020
  • В любом случае, я решил это, как мне кажется, наименее уродливым способом справиться с этим. 15.09.2020
  • Это так работает, потому что кто-то так сделал :-) Очевидно, они либо не думали об этом, либо думали, что так будет лучше, либо имели другие причины. Возможно, это сказала жена босса, когда была пьяна. Вы найдете много-много-много-много-много-много больших глупостей в программном обеспечении, так что если это ваша единственная проблема, считайте себя благословленным... 15.09.2020

  • 3

    Рассматривали ли вы возможность использования пула соединений.

    # Create Connection Pool
    pool = mariadb.ConnectionPool(
          #...,
          pool_size=1
       )
    
    

    Затем в вашем методе подключения.

    try:
        pconn = pool.get_connection()
    
    except mariadb.PoolError as e:
       # Report Error
       print(f"Error opening connection from pool: {e}")
    

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

    Я получил код из их документов

    15.09.2020
  • Вы также упомянули об этом в комментариях к моему вопросу. Я думаю, что это одно из лучших решений, но все же мне кажется, что это слишком много. Такое ощущение, что за кадром, может 4, 8, 16? устанавливаются соединения, и каждый раз, когда вы получаете одно из них. Мне нужно только одно соединение. Наличие нескольких подключений под рукой не обязательно и кажется пустой тратой времени. 15.09.2020
  • Хорошо, вы только что добавили код для pool_size. Это, конечно, способ, но опять же, какой-то громоздкий объект управления, который не должен быть необходим на таком языке, как Python imo. 15.09.2020
  • Громоздкий является относительным, плюс он использует пул соединений mariadb, поэтому он может быть очень эффективным. mariadb — API довольно низкого уровня. Он соответствует API Python db, поэтому его можно использовать с библиотеками dp более высокого уровня. Как sqlalchemy, где вам не нужно знать фактическую используемую базу данных. 15.09.2020
  • Это правда, конечно. В прошлом я писал код обработки mysql на C и C++ (где я действительно ожидаю этих проблем) и каким-то образом я ожидал, что реализация Python будет более высокого уровня. 15.09.2020
  • Новые материалы

    Расистский и сексистский робот, обученный в Интернете
    Его ИИ основан на предвзятых данных, которые создают предрассудки. Он словно переходит из одного эпизода в другой из серии Черное зеркало , а вместо этого представляет собой хронику..

    Управление состоянием в микрофронтендах
    Стратегии бесперебойного сотрудничества Микро-фронтенды — это быстро растущая тенденция в сфере фронтенда, гарантирующая, что удовольствие не ограничивается исключительно бэкэнд-системами..

    Декларативное и функциональное программирование в стиле LINQ с использованием JavaScript с использованием каррирования и генератора ...
    LINQ - одна из лучших функций C #, которая обеспечивает элегантный способ написания кода декларативного и функционального стиля, который легко читать и понимать. Благодаря таким функциям ES6,..

    Структуры данных в C ++ - Часть 1
    Реализация общих структур данных в C ++ C ++ - это расширение языка программирования C, которое поддерживает создание классов, поэтому оно известно как C с классами . Он используется для..

    Как я опубликовал свое первое приложение в App Store в 13 лет
    Как все началось Все началось три года назад летом после моего четвертого класса в начальной школе. Для меня, четвертого класса, лето кажется бесконечным, пока оно не закончится, и мой отец..

    Что в лицо
    Очерк о возвращении физиогномики и о том, почему мы должны это приветствовать. История начинается со странной науки. Р. Тора Бьорнсдоттир, Николас О. Рул. Видимость социального класса по..

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