НОВОЕ 📢 Ограниченное стартовое предложение: скидка до 70% на первый месяц для любой игры!Посмотреть тарифы

Как подключить базу данных MySQL к боту на python-telegram-bot (aiomysql)

Хостинг Telegram-ботовUpdated Nov 6, 202611 min read

Коротко. Добавьте aiomysql в своего бота на python-telegram-bot, откройте один пул с autocommit=True в post_init, храните его в application.bot_data, чтобы каждый обработчик мог до него дотянуться, и закройте его в post_shutdown. Используйте плейсхолдеры %s и храните ID Telegram как BIGINT, а не INT. В PlayNHost каждый тариф для бота включает одну базу MySQL: создайте её на странице Базы данных, вставьте её данные на панель Окружение, и код ниже заработает как есть. База принимает подключения с ваших серверов PlayNHost и из phpMyAdmin, но не с вашего компьютера. Мы проверили этот код 6 ноября 2026 года с python-telegram-bot 22.8, aiomysql 0.3.2 и Python 3.12, на MariaDB 10.11 с учётной записью базы, настроенной как у нас (20 подключений, лимит запроса 120 секунд), и заменой сервера Bot API Telegram: 8 команд на входе, 8 ответов на выходе, заметки сохранены в базе. Реальный вход в Telegram в тест не входил.

Что вам понадобится

1. Создайте базу данных

  1. Откройте сервер своего бота в панели и нажмите Базы данных.
  2. Нажмите Новая база данных. Метка необязательна; Будет создана как показывает полное имя. Нажмите Создать базу данных.
  3. Откроется страница новой базы (позже нажмите на неё в списке Ваши базы данных). Карточка Подключение показывает Хост, Порт, База данных, Имя пользователя и Пароль. В разделе Готовый конфиг в поле Конфиг для есть фрагмент Python (aiomysql) с уже подставленными теми же значениями.

2. Внесите данные в Окружение

  1. Откройте страницу Бот, панель Окружение, и нажмите Вставить файл .env.
  2. Вставьте эти пять строк со значениями из карточки Подключение:
    DB_HOST=your-host
    DB_PORT=3306
    DB_NAME=your-database
    DB_USER=your-username
    DB_PASSWORD=your-password
  3. DB_PASSWORD получает пометку Секрет: значение сохраняется только на запись и больше никогда не показывается. Нажмите Импортировать 5 переменных.

Берите реальный порт из карточки Подключение. Токена бота в этих строках нет: токен, который вы сохранили на странице Бот, панель Telegram, уже попадает в ваш код как TELEGRAM_BOT_TOKEN, то есть под именем, которое читает код ниже (а также как BOT_TOKEN, TOKEN, TELEGRAM_TOKEN и TELOXIDE_TOKEN). Если в вашем файле .env есть строка с токеном, диалог её пропускает. Подробнее об этой панели: Как задать переменные окружения и секреты для Discord-бота.

3. Код

Небольшой бот для заметок: /note buy milk сохраняет заметку, /notes показывает ваши последние пять.

requirements.txt

python-telegram-bot>=22,<23
aiomysql

bot.py

import os

import aiomysql
from telegram import Update
from telegram.ext import Application, CommandHandler, ContextTypes


async def post_init(application: Application) -> None:
    # One pool for the whole bot. The values come from the Environment pane.
    pool = await aiomysql.create_pool(
        host=os.environ["DB_HOST"],
        port=int(os.environ["DB_PORT"]),
        user=os.environ["DB_USER"],
        password=os.environ["DB_PASSWORD"],
        db=os.environ["DB_NAME"],
        autocommit=True,
        maxsize=5,
    )
    async with pool.acquire() as conn:
        async with conn.cursor() as cur:
            await cur.execute(
                "CREATE TABLE IF NOT EXISTS notes ("
                "id INT AUTO_INCREMENT PRIMARY KEY, "
                "user_id BIGINT NOT NULL, "
                "text VARCHAR(500) NOT NULL, "
                "created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "
                "KEY user_id (user_id))"
            )
    application.bot_data["pool"] = pool
    print("Database ready")
    print(f"Logged in as @{application.bot.username}")


async def post_shutdown(application: Application) -> None:
    pool = application.bot_data.get("pool")
    if pool is not None:
        pool.close()
        await pool.wait_closed()


async def note(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    text = " ".join(context.args).strip()
    if not text:
        await update.effective_message.reply_text("Write your note after the command, for example: /note buy milk")
        return
    if len(text) > 500:
        await update.effective_message.reply_text("A note can be up to 500 characters.")
        return
    pool = context.bot_data["pool"]
    async with pool.acquire() as conn:
        async with conn.cursor() as cur:
            await cur.execute(
                "INSERT INTO notes (user_id, text) VALUES (%s, %s)",
                (update.effective_user.id, text),
            )
    await update.effective_message.reply_text("Saved. Send /notes to see your last five.")


async def notes(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    pool = context.bot_data["pool"]
    async with pool.acquire() as conn:
        async with conn.cursor() as cur:
            await cur.execute(
                "SELECT text FROM notes WHERE user_id = %s ORDER BY id DESC LIMIT 5",
                (update.effective_user.id,),
            )
            rows = await cur.fetchall()
    if not rows:
        await update.effective_message.reply_text("You have no notes yet.")
        return
    await update.effective_message.reply_text("\n".join(f"- {text}" for (text,) in rows))


def main() -> None:
    application = (
        Application.builder()
        .token(os.environ["TELEGRAM_BOT_TOKEN"])
        .post_init(post_init)
        .post_shutdown(post_shutdown)
        .build()
    )
    application.add_handler(CommandHandler("note", note))
    application.add_handler(CommandHandler("notes", notes))
    application.run_polling()


if __name__ == "__main__":
    main()

Что он делает: run_polling() сначала спрашивает у Telegram, кто такой бот, затем запускает post_init и только после этого начинает получать сообщения. post_init открывает пул, создаёт таблицу notes, если её нет, кладёт пул в application.bot_data["pool"] и выводит Database ready и Logged in as @your_bot. Каждый обработчик получает тот же словарь как context.bot_data. Когда бот останавливается, run_polling() запускает post_shutdown, которая закрывает пул.

context.args содержит текст после команды, разбитый на слова, поэтому код склеивает его обратно одинарными пробелами: переводы строк и двойные пробелы внутри заметки превращаются в один пробел. Если вам нужна заметка в точности так, как её набрали, возьмите update.effective_message.text и отрежьте команду сами.

Плейсхолдеры %s подставляет драйвер, поэтому заметка вроде '); DROP TABLE notes; -- сохраняется как обычный текст. В нашем тесте она сохранилась как строка 2, а таблица осталась на месте. Никогда не собирайте SQL через f-строку.

Храните ID Telegram как BIGINT

В документации Telegram Bot API сказано, что ID пользователя или чата «может содержать более 32 значащих бит», но не больше 52, поэтому 64-битное целое надёжно. Страница об ID даёт диапазоны: ID пользователей доходят до 1.099.511.627.775, а ID групп, супергрупп и каналов отрицательные. Супергруппы и каналы лежат в диапазоне от -1.000.000.000.001 до -1.997.852.516.352 (например, -1001234567890). INT в MySQL заканчивается на 2.147.483.647. В нашем тесте, в том же строгом режиме SQL, который использует наш сервер баз данных, сохранить ID пользователя 7.000.000.001 в столбце INT не удалось. aiomysql выдал:

pymysql.err.DataError: (1264, "Out of range value for column 'user_id' at row 1")

Столбец BIGINT его сохранил, и -1001234567890 тоже. Берите обычный BIGINT, а не BIGINT UNSIGNED, чтобы поместились и ID чатов.

4. Запустите

  1. Загрузите bot.py и requirements.txt в разделе Файлы.
  2. Нажмите Запустить и откройте Консоль. При запуске устанавливаются пакеты из requirements.txt, затем выводятся [PlayNHost] Starting your bot, Database ready и Logged in as @your_bot. Эта последняя строка переключает статус в разделе Обзор на Онлайн в Telegram.
  3. В Telegram отправьте боту /note buy milk, затем /notes. Чтобы посмотреть строки, откройте страницу базы и нажмите Открыть в phpMyAdmin.

Начиная со второго запуска консоль также показывает Warning: Table 'notes' already exists, перед которым стоит путь к cursors.py из aiomysql. Это примечание MariaDB для CREATE TABLE IF NOT EXISTS, которое драйвер передаёт дальше. Ничего не сломано.

В нашем тесте замена Telegram отправила эти команды и получила эти ответы:

ОтправленоОтвет
/note buy milkSaved. Send /notes to see your last five.
/noteWrite your note after the command, for example: /note buy milk
/note и 501 символA note can be up to 500 characters.
/notesСохранённые заметки, сначала новые
/notes от второго пользователяYou have no notes yet.

Одно обновление за раз

С настройками по умолчанию python-telegram-bot обрабатывает одно обновление за раз: следующая команда ждёт, пока закончит предыдущий обработчик. В нашем тесте 4 команды, пришедшие вместе, получили ответы в порядке поступления, а после теста у бота было 1 открытое подключение. maxsize=5 всё равно важен, если вы включите concurrent_updates в Application.builder() или если задачи и обработчики обращаются к базе одновременно: пул никогда не открывает больше 5 подключений, а лишний запрос ждёт свободного.

Ловушка autocommit

aiomysql открывает подключения с выключенным autocommit. Тогда INSERT остаётся внутри открытой транзакции. Когда подключение возвращается в пул с ещё открытой транзакцией, aiomysql закрывает его, а MariaDB отбрасывает неподтверждённую строку. В нашем тесте строку, вставленную в пул без autocommit=True, увидели 0 других подключений. Либо передайте autocommit=True в create_pool, как выше, либо вызывайте await conn.commit() после записи.

Неудачный запрос не останавливает бота

В нашем тесте мы удалили таблицу, пока бот работал, и отправили /notes. python-telegram-bot вывел No error handlers are registered, logging exception. и трассировку, заканчивающуюся на pymysql.err.ProgrammingError: (1146, "Table '...notes' doesn't exist"), не отправил ответа на эту команду и продолжил работать: когда таблица вернулась, следующая /note сохранилась. Человек, отправивший команду, ничего не видит, поэтому перехватывайте ошибку в обработчике или зарегистрируйте обработчик ошибок через application.add_error_handler() и отвечайте коротким сообщением.

Тихая ночь не ломает пул

Наш сервер баз данных закрывает подключение, которое простояло без дела 8 часов. Бот, которому так долго никто не пишет, например ночью, обнаруживает, что его подключения в пуле закрыты. aiomysql проверяет это, когда обработчик просит подключение, и открывает новое вместо закрытого. Мы проверили это с лимитом простоя, сниженным до 5 секунд: после 7 тихих секунд следующая /note сохранилась без ошибки.

Лимиты, важные для вашего пула

  • 20 одновременных подключений на пользователя базы. Считается каждый процесс, использующий тот же логин. Подключение 21 отклоняется с (1226, "User '...' has exceeded the 'max_user_connections' resource (current value: 20)"). maxsize=5 держит бота намного ниже этого лимита.
  • Запрос, который выполняется дольше 120 секунд, прерывается. MariaDB завершает его с ошибкой 1969, Query execution was interrupted (max_statement_time exceeded). В нашем более раннем тесте, на учётной записи, настроенной так же, это произошло через 120,03 секунды. Запросы бота обычно занимают миллисекунды, так что это касается только запроса, пошедшего вразнос.
  • 500 MB на базу данных. Страница Базы данных показывает, сколько занято. При 500 MB запись останавливается, пока вы не удалите строки.
  • Резервные копии сервера не включают базу данных. Время от времени экспортируйте её из phpMyAdmin.

Ошибки и что они значат

Это ошибки, которые aiomysql выдал в нашем тесте, записанные так, как они завершают трассировку в консоли (они приходят из PyMySQL, на котором работает aiomysql):

  • pymysql.err.OperationalError: (1045, "Access denied for user ..."): неверный DB_USER или DB_PASSWORD. После Сбросить пароль в карточке Подключение старый пароль перестаёт работать сразу: замените DB_PASSWORD и перезапустите бота.
  • pymysql.err.OperationalError: (2003, "Can't connect to MySQL server on ..."): неверный хост или порт либо подключение извне PlayNHost. База принимает подключения только с серверов PlayNHost и из phpMyAdmin, поэтому то же самое вы получите и со своего компьютера.
  • pymysql.err.OperationalError: (1226, ... 'max_user_connections' ...): открыто слишком много подключений, см. лимиты выше. Например, вторая копия бота или пул, открываемый на каждое сообщение.
  • pymysql.err.DataError: (1264, "Out of range value for column 'user_id' at row 1"): столбец с ID имеет тип INT. Измените его на BIGINT.
  • pymysql.err.ProgrammingError: (1146, "Table '...' doesn't exist"): таблицы нет. Код выше создаёт её при запуске; если вы создали её вручную, проверьте имя.

Если в Окружение не хватает переменной, бот останавливается до подключения с KeyError: 'DB_HOST' или с именем недостающей, потому что код читает каждое значение через os.environ[...]. Добавьте её и перезапустите бота: переменные применяются при следующем запуске.

Неверный токен останавливает бота до части с базой, потому что post_init запускается только после того, как Telegram принял токен. С нашим поддельным токеном против настоящего Telegram бот ни разу не вывел Database ready и остановился примерно через 1 секунду с telegram.error.InvalidToken: The token `...` was rejected by the server. Это сообщение содержит токен целиком, поэтому не публикуйте вывод консоли с ним нигде на виду. Скопируйте токен заново у @BotFather и вставьте его на странице Бот, панель Telegram. Другие ошибки Telegram: Как исправить ошибку 409 Conflict у Telegram-бота.

Сколько памяти он использует

В нашем тесте работающий бот использовал 48,5 MB памяти (резидентной) после ответа на 8 команд, с открытым пулом, в контейнере с лимитом 512 MB, как у нашего самого маленького тарифа для бота. В нашем более раннем тесте хостинга один python-telegram-bot занимал 44 MB (тот бот остановился на отклонённом токене, не успев обработать ни одного сообщения), так что разница, около 4,5 MB, приходится на aiomysql, пул и обработанные команды. Установка python-telegram-bot и aiomysql через pip с нуля достигла пика 73 MB вместе с файловым кэшем. Библиотеки рядом: python-telegram-bot или aiogram на PlayNHost.

То же самое с другой библиотекой

Нужен сервер для вашего бота в Telegram с включённой базой данных? Посмотрите тарифы для ботов в Telegram.

Ваш бот уже у другого хостинга? Перенесите его к нам

ПОСЛЕДНИЕ БЛОГИ