#python-3.x #exception #mariadb
#python-3.x #исключение #mariadb
Вопрос:
Итак, я разрабатываю эту онлайн-игру, похожую на telnet, и она не очень популярна (кто знает, когда-нибудь), поэтому подключение к базе данных моего игрового движка не используется в течение нескольких часов ночью. Это один скрипт, который ожидает событий, поэтому он продолжает выполняться.
При первом выполнении запроса после нескольких часов бездействия я получаю ошибку mariadb.DatabaseError при попытке запустить курсор. Если я повторю запрос, он снова заработает. Таким образом, хотя функция выдает исключение о том, что соединение потеряно, она восстанавливает его.
Мой вопрос: как мне с этим справиться?
Это вещи, которые я рассматриваю как возможные решения, но, на мой взгляд, они не очень хороши:
- перенос каждого запроса в структуру, исключающую попытки, делает код громоздким из-за большей части ненужного и повторяющегося кода
- пишу свою собственную функцию ‘decorator’ для выполнения запроса, который затем повторно инициализирует базу данных, когда я получу 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, но никаких действий предпринимать не нужно, потому что, если я запрашиваю с тем же подключением к базе данных, оно снова работает. Таким образом, соединение само восстанавливается. Почему компонент вызывает у меня запрос, в то время как он «восстанавливает» само соединение и может запрашивать снова?
Но поскольку это то, что есть, мой вопрос: каков наилучший способ справиться с этим?
Комментарии:
1. Я действительно не хочу писать блок try-excet вокруг каждого запроса и цикл while вокруг него, чтобы повторить попытку, пока она не сработает… Кроме того, писать для него определенную функцию глупо из-за ошибки, которую я не могу контролировать.
2. @matt почему оставлять соединение с БД открытым для одного приложения, которое нуждается в нем время от времени, нехорошо? Когда я должен закрыть / повторно открыть это соединение? После 5 запросов? Через 5 минут? Очень непредсказуемо, как будет использоваться база данных, потому что это сервер обработки запросов для всех, кто входит в игру… Повторное открытие соединения с БД для каждого запроса мне тоже кажется неправильным.
3. Я повторю: «Проблема может заключаться в том, что ваша база данных будет заполняться открытыми соединениями, когда приложения их не закрывают».
4. Пользователи работают намного медленнее, чем БД. В любое время, когда вам нужно дождаться ввода пользователем, целесообразно открыть / закрыть. Также вы можете заглянуть в пул соединений. Они могут управлять поддержанием активного соединения.
5. Извините @matt за то, что не так ясно, но я знаю все эти вещи, о которых вы говорите. Я годами писал программное обеспечение для БД и решал эти проблемы с помощью таких вещей, как функции декоратора. Я хочу сказать, что в этом не должно быть необходимости. Мой сервер также имеет постоянное открытое соединение redis, где он прослушивает события. Почему соединение с БД должно прерваться через некоторое время и почему оно не должно восстанавливаться автоматически? Я понимаю, что могу заглянуть в свои настройки mariadb, но тогда это нужно помнить каждый раз, когда вы меняете db. Мой вопрос заключается в надежде найти правильное решение для этого.
Ответ №1:
Если вы установите значение long time out, даже нет гарантии, что соединение будет прервано по другим причинам (тайм-аут клиента, отключение на 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 установит новое соединение перед выполнением инструкции. Это решение не будет работать, если вы используете существующий открытый курсор, поскольку дескриптор внутренней инструкции становится недействительным.
Комментарии:
1. БОЖЕ! Я не знал, что это существует! Да, разорванное соединение во время использования курсора — это не то, что я пытаюсь исправить. Но автоматическое исправление разорванного соединения перед выполнением запроса — это то, что мне нужно. Где вы нашли эту информацию? Я не нашел реального API для соединителя mariadb. Вы знаете, где я могу это найти?
2. Поскольку я являюсь автором / сопровождающим MariaDB Connector / Python, найти его было нетрудно 🙂 Это также задокументировано на mariadb-corporation.github.io/mariadb-connector-python /…
3. Ха-ха. Отлично! Спасибо!
4. Хм, однако, похоже, это не сработало, потому что сегодня у меня снова возникла ошибка при первом запросе после нескольких часов бездействия. Или я неправильно понимаю? Не должно ли это помешать вам получить ошибку и не следует ли автоматически повторить попытку?
Ответ №2:
Используете ли вы сокет или TCP / IP для подключения?
Соединения TCP / IP предназначены для очистки после периода отсутствия трафика. Вы можете сказать, что это идиотизм, но на самом деле нет лучшего способа узнать, происходит ли сбой программы.
По той же причине базы данных имеют свой собственный механизм ожидания. Для MySQL это называется wait_timeout.
Обычно объект соединения (или его оболочка) позаботится о выполнении некоторого запроса без операции, если с соединением больше ничего не происходит, что-то вроде select 1
. Это стандартная практика. Проверьте документацию для вашего объекта connection — возможно, он уже есть, вам просто нужно его настроить. Используйте что-то вроде 30-60 секунд.
Если нет, вам придется реализовать это самостоятельно. Не имеет значения, как, дело в том, что вы не можете ожидать, что соединения будут оставаться открытыми вечно. Либо сделайте соединения недолговечными (открывайте их только тогда, когда вам это нужно, и закрывайте их впоследствии), либо реализуйте таймер, который будет периодически вставлять какой-либо запрос без операции. В последнем случае обратите внимание, что вам нужно будет реализовать механизм синхронизации, чтобы убедиться, что запрос вашего приложения никогда не выполняется одновременно с запросом без операции.
Комментарии:
1. Да, вы, конечно, правы, и я бы не ожидал этого иначе. Но чего я не понимаю, так это того, что mariadb в python не обрабатывает это автоматически. Он повторно подключается после сбоя (мне не нужно это делать), поэтому, если вы выполняете запрос, он выдает исключение, и если вы затем запрашиваете снова, он снова работает. Почему реализация не делает это немедленно для вас? Вот что я имею в виду под идиотизмом.
2. В любом случае, я решил это, как мне кажется, наименее некрасивым способом справиться с этим.
3. Это работает так, потому что кто-то сделал это так 🙂 Очевидно, что они либо не думали об этом, либо думали, что так будет лучше, либо имели другие причины. Возможно, это было то, что сказала жена босса, когда она была пьяна. Вы найдете много, много, много, много, много, много больших глупостей в программном обеспечении, поэтому, если это ваша единственная проблема, считайте, что вы благословлены…
Ответ №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}")
В документации не говорится, что происходит, когда соединения закрыты или разорваны. Я ожидаю, что он позаботится об этом и всегда пытается обеспечить действительное соединение (до тех пор, пока вы не запрашиваете больше соединений, чем в пуле.)
Я получил код из их документов
Комментарии:
1. Вы также упомянули об этом в комментариях к моему вопросу. Я думаю, что это одно из лучших решений, но все же, я чувствую, что это слишком много. Такое ощущение, что за сценой, может быть, 4, 8, 16? создаются соединения, и каждый раз, когда вы получаете одно из них. Мне нужно только одно соединение. Наличие нескольких подключений под рукой не является необходимым и кажется пустой тратой времени.
2. Хорошо, вы только что добавили код для pool_size. Конечно, это правильный путь, но опять же, какой-то громоздкий объект управления, который не должен быть необходим на языке, подобном Python imo.
3. Объемность относительна, плюс она использует пул соединений mariadb, поэтому она может быть очень эффективной. mariadb — довольно низкоуровневый API. Он соответствует API python db, поэтому его можно использовать с библиотеками dp более высокого уровня. Как sqlalchemy, где вам не нужно знать фактическую используемую базу данных.
4. Конечно, это правда. В прошлом я писал код обработки mysql на C и C (где я действительно ожидал этих проблем), и почему-то я ожидал, что реализация Python будет более высокого уровня.