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

Как использовать базу данных MySQL с ботом discord.py (aiomysql)

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

Коротко. Добавьте aiomysql в своего бота discord.py, создайте один пул в setup_hook с autocommit=True и выполняйте все запросы через него с плейсхолдерами %s. Без autocommit=True (или commit() после каждой записи) aiomysql не сохраняет ваши INSERT. На PlayNHost каждый тариф для бота включает одну базу MySQL: создайте её на странице Базы данных, вставьте её данные во вкладку Окружение, и код ниже заработает как есть. База принимает подключения с ваших серверов PlayNHost и из phpMyAdmin, но не с вашего компьютера. Мы проверили этот код 6 ноября 2026 года с discord.py 2.7.1, aiomysql 0.3.2 и Python 3.12 на MariaDB 10.11 с учётной записью базы, настроенной как у нас (20 подключений, лимит запроса 120 секунд): запуск, таблицу и команду /warn, но не настоящий вход в Discord.

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

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 переменных.

Используйте настоящий порт с карточки Подключение. Ваш код читает значения через os.environ, поэтому пароль никогда не лежит в файлах бота. Подробнее об этом разделе: Как задать переменные окружения и секреты для Discord-бота.

3. Код

requirements.txt

discord.py
aiomysql

bot.py

import os

import aiomysql
import discord
from discord import app_commands


class Bot(discord.Client):
    def __init__(self):
        super().__init__(intents=discord.Intents.default())
        self.tree = app_commands.CommandTree(self)
        self.pool = None

    async def setup_hook(self):
        # One pool for the whole bot. The values come from the Environment pane.
        self.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 self.pool.acquire() as conn:
            async with conn.cursor() as cur:
                await cur.execute(
                    "CREATE TABLE IF NOT EXISTS warnings ("
                    "id INT AUTO_INCREMENT PRIMARY KEY, "
                    "guild_id BIGINT UNSIGNED NOT NULL, "
                    "user_id BIGINT UNSIGNED NOT NULL, "
                    "reason VARCHAR(200) NOT NULL, "
                    "created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "
                    "KEY guild_user (guild_id, user_id))"
                )
        print("Database ready")
        await self.tree.sync()

    async def close(self):
        if self.pool is not None:
            self.pool.close()
            await self.pool.wait_closed()
        await super().close()


bot = Bot()


@bot.tree.command(description="Warn a member and count their warnings")
@app_commands.guild_only()
@app_commands.describe(user="Who to warn", reason="Why")
async def warn(interaction: discord.Interaction, user: discord.Member, reason: app_commands.Range[str, 1, 200]):
    async with bot.pool.acquire() as conn:
        async with conn.cursor() as cur:
            await cur.execute(
                "INSERT INTO warnings (guild_id, user_id, reason) VALUES (%s, %s, %s)",
                (interaction.guild_id, user.id, reason),
            )
            await cur.execute(
                "SELECT COUNT(*) FROM warnings WHERE guild_id = %s AND user_id = %s",
                (interaction.guild_id, user.id),
            )
            (total,) = await cur.fetchone()
    await interaction.response.send_message(f"{user.mention} has {total} warning(s).")


@bot.event
async def on_ready():
    print(f"Logged in as {bot.user}")


bot.run(os.environ["DISCORD_TOKEN"])

Что делает код: discord.py выполняет setup_hook один раз, во время входа и до прихода любых событий, поэтому пул и таблица warnings существуют до появления первой команды. Затем он регистрирует /warn в Discord. @app_commands.guild_only() не пускает команду в личные сообщения, где нет сервера, к которому можно привязать предупреждение. ID Discord помещаются в BIGINT UNSIGNED. Плейсхолдеры %s подставляет драйвер, поэтому причина вроде '); DROP TABLE warnings; -- сохраняется как обычный текст. Никогда не собирайте SQL через f-строку.

4. Запуск

  1. Загрузите bot.py и requirements.txt в разделе Файлы.
  2. Нажмите Запустить и откройте Консоль. При запуске устанавливаются пакеты из requirements.txt, затем появляются [PlayNHost] Starting your bot и Database ready.
  3. В Discord введите /warn. Чтобы посмотреть строки, откройте страницу базы данных и нажмите Открыть в phpMyAdmin.

Ловушка autocommit

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

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

  • 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, поэтому то же самое вы получите и на своём компьютере.
  • (1226, ... 'max_user_connections' ...): открыто слишком много подключений, см. лимиты выше.

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

Другие ошибки ботов: Частые ошибки Discord-ботов и что они означают.

Сколько памяти это добавляет

Установка discord.py и aiomysql через pip с нуля дала в нашем тесте пик в 78 MB памяти в контейнере с лимитом 512 MB, как у нашего самого маленького тарифа для ботов. Сколько памяти нужно работающему боту, зависит в основном от числа серверов, на которых он находится: Сколько RAM нужно Discord-боту

То же самое на JavaScript или с Prisma

Нужен сервер для бота с базой данных в комплекте? Посмотреть тарифы для ботов.

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

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