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

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

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

Коротко. Добавьте mysql2 в своего бота discord.js, при запуске бота создайте один пул подключений из переменных окружения и выполняйте все запросы через него с плейсхолдерами ?. На PlayNHost каждый тариф для бота включает одну базу MySQL: создайте её на странице Базы данных, вставьте её данные во вкладку Окружение, и код ниже заработает как есть. База принимает подключения с ваших серверов PlayNHost и из phpMyAdmin, но не с вашего компьютера. Мы проверили этот код 6 ноября 2026 года с discord.js 14.28.1, mysql2 3.24.6 и Node.js 22 на MariaDB 10.11 с учётной записью базы, настроенной как у нас (20 подключений, лимит запроса 120 секунд): запуск, таблицу и обработчик /warn, но не настоящий вход в Discord.

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

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

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

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

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

3. Код

package.json

{
  "name": "my-discord-bot",
  "private": true,
  "main": "index.js",
  "dependencies": {
    "discord.js": "^14",
    "mysql2": "^3"
  }
}

index.js

const { Client, Events, GatewayIntentBits, InteractionContextType, MessageFlags, REST, Routes, SlashCommandBuilder } = require('discord.js');
const mysql = require('mysql2/promise');

// One pool for the whole bot. The values come from the Environment pane.
const pool = mysql.createPool({
  host: process.env.DB_HOST,
  port: Number(process.env.DB_PORT),
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
  connectionLimit: 5,
});

const commands = [
  new SlashCommandBuilder()
    .setName('warn')
    .setDescription('Warn a member and count their warnings')
    .setContexts(InteractionContextType.Guild)
    .addUserOption((o) => o.setName('user').setDescription('Who to warn').setRequired(true))
    .addStringOption((o) => o.setName('reason').setDescription('Why').setRequired(true).setMaxLength(200))
    .toJSON(),
];

const client = new Client({ intents: [GatewayIntentBits.Guilds] });

client.once(Events.ClientReady, async () => {
  await new REST().setToken(process.env.DISCORD_TOKEN).put(Routes.applicationCommands(client.user.id), { body: commands });
  console.log(`Logged in as ${client.user.tag}`);
});

client.on(Events.InteractionCreate, async (interaction) => {
  if (!interaction.isChatInputCommand() || interaction.commandName !== 'warn' || !interaction.guildId) return;
  const user = interaction.options.getUser('user', true);
  const reason = interaction.options.getString('reason', true);
  try {
    await pool.execute('INSERT INTO warnings (guild_id, user_id, reason) VALUES (?, ?, ?)', [interaction.guildId, user.id, reason]);
    const [rows] = await pool.execute('SELECT COUNT(*) AS total FROM warnings WHERE guild_id = ? AND user_id = ?', [interaction.guildId, user.id]);
    await interaction.reply(`${user} has ${rows[0].total} warning(s).`);
  } catch (err) {
    console.error(err);
    if (!interaction.replied) await interaction.reply({ content: 'Could not save the warning. Try again later.', flags: MessageFlags.Ephemeral }).catch(console.error);
  }
});

async function main() {
  await pool.query(`CREATE TABLE IF NOT EXISTS warnings (
    id INT AUTO_INCREMENT PRIMARY KEY,
    guild_id VARCHAR(20) NOT NULL,
    user_id VARCHAR(20) NOT NULL,
    reason VARCHAR(200) NOT NULL,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    KEY guild_user (guild_id, user_id)
  )`);
  console.log('Database ready');
  await client.login(process.env.DISCORD_TOKEN);
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});

Что делает код: при запуске бот создаёт таблицу warnings, если её нет, выводит Database ready и затем входит в Discord. /warn сохраняет предупреждение и отвечает общим числом предупреждений участника. discord.js отдаёт ID Discord строками, потому что они слишком велики для числа JavaScript, поэтому они хранятся как текст. Плейсхолдеры ? отправляют текст участника в базу отдельно от SQL, поэтому причина вроде '); DROP TABLE warnings; -- сохраняется как обычный текст.

Две строки не дают боту упасть. setContexts(InteractionContextType.Guild) и проверка !interaction.guildId запрещают /warn в личных сообщениях, где нет сервера, к которому можно привязать предупреждение. Блок try/catch отвечает коротким приватным сообщением, если запрос не удался. Без него discord.js передаёт ошибку своему событию error, а если его никто не слушает, Node.js останавливает бота. В нашем тесте мы удалили таблицу и выполнили /warn: бот записал в лог ER_NO_SUCH_TABLE, ответил Could not save the warning. Try again later. и продолжил работать.

4. Запуск

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

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

  • 20 одновременных подключений на пользователя базы. Считается каждый процесс с тем же логином: ваш бот, его вторая копия, панель управления. Подключение 21 отклоняется с ошибкой ER_USER_LIMIT_REACHED. В нашем тесте пул с connectionLimit: 30 упал с этой ошибкой, как только 30 запросов выполнились одновременно. Пул из 5 никогда не открывает больше 5: лишние запросы ждут свободного подключения.
  • Запрос, который выполняется дольше 120 секунд, прерывается. MariaDB завершает его с ошибкой 1969, Query execution was interrupted (max_statement_time exceeded). В нашем тесте это произошло через 120.03 секунды. Запросы бота обычно занимают миллисекунды, поэтому это касается только зависшего запроса.
  • 500 MB на базу данных. На странице Базы данных видно, сколько занято. При 500 MB запись блокируется, пока вы не удалите строки.
  • Резервные копии сервера не включают базу данных. Время от времени экспортируйте её из phpMyAdmin.

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

Это точные сообщения, которые mysql2 выдал в нашем тесте:

  • ER_ACCESS_DENIED_ERROR ... Access denied for user: неверный DB_USER или DB_PASSWORD. После Сбросить пароль на карточке Подключение старый пароль перестаёт работать сразу: замените DB_PASSWORD и перезапустите бота.
  • ECONNREFUSED: по этому хосту и порту подключение никто не принял. Проверьте DB_HOST и DB_PORT. Отсутствующий DB_HOST тоже приводит сюда, потому что mysql2 тогда подключается к localhost, где никакая база не работает: переменной нет во вкладке Окружение, или бота не перезапустили после её добавления.
  • ENOTFOUND: DB_HOST написан с ошибкой, поэтому такого имени не существует.
  • ETIMEDOUT: за 10 секунд никто не ответил, это ожидание mysql2 по умолчанию. Какое бы ни было сообщение, подключение с вашего компьютера не сработает: база принимает подключения только с серверов PlayNHost и из phpMyAdmin.
  • ER_USER_LIMIT_REACHED ... has exceeded the 'max_user_connections' resource (current value: 20): открыто слишком много подключений, см. лимиты выше. Обычно это новое подключение на каждую команду, которое никогда не закрывается, или вторая копия бота.

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

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

Установка discord.js и mysql2 с нуля дала в нашем тесте пик в 147 MB памяти (кэш файлов не считается) в контейнере с лимитом 512 MB, как у нашего самого маленького тарифа для ботов. От запуска до момента, когда Discord отклонил наш тестовый токен, бот использовал не больше 31 MB; он создал таблицу, но так и не вошёл в Discord. Бот, который вошёл и находится на многих серверах, использует больше: Сколько RAM нужно Discord-боту

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

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

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

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