Коротко. Добавьте 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.
Откройте сервер вашего бота в панели и нажмите Базы данных.
Нажмите Новая база данных. Поле Метка необязательно; в поле Будет создана как показано полное имя. Нажмите Создать базу данных.
Откроется страница новой базы (позже её можно открыть в разделе Ваши базы данных). На карточке Подключение указаны Хост, Порт, База данных, Имя пользователя и Пароль. В разделе Готовый конфиг в списке Конфиг для есть сниппет Python (aiomysql) с теми же уже подставленными значениями.
2. Сохраните данные в разделе Окружение
Откройте страницу Бот, вкладку Окружение, и нажмите Вставить файл .env.
Вставьте эти пять строк со значениями с карточки Подключение:
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. Запуск
Загрузите bot.py и requirements.txt в разделе Файлы.
Нажмите Запустить и откройте Консоль. При запуске устанавливаются пакеты из requirements.txt, затем появляются [PlayNHost] Starting your bot и Database ready.
В 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.py и aiomysql через pip с нуля дала в нашем тесте пик в 78 MB памяти в контейнере с лимитом 512 MB, как у нашего самого маленького тарифа для ботов. Сколько памяти нужно работающему боту, зависит в основном от числа серверов, на которых он находится: Сколько RAM нужно Discord-боту
Мы используем файлы cookie, чтобы сайт работал, чтобы понимать, как им пользуются, и чтобы оценивать наш маркетинг. Вы можете принять всё, принять только необходимые файлы cookie или выбрать по категориям.Мы используем файлы cookie, чтобы сайт работал, чтобы понимать, как им пользуются, и чтобы оценивать наш маркетинг. Здесь они включены по умолчанию. Вы можете отключить всё, что не хотите.Читать политику конфиденциальности