جديد 📢 عرض الإطلاق لفترة محدودة: خصم حتى 70% على شهرك الأول في كل لعبة.عرض الباقات

كيف تستخدم قاعدة بيانات MySQL مع بوت discord.py (aiomysql)

استضافة بوتات DiscordUpdated 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() الأمر بعيدًا عن الرسائل الخاصة، حيث لا يوجد خادم يُحفظ التحذير له. تتسع معرّفات Discord في BIGINT UNSIGNED. يملأ المشغّل العناصر النائبة %s، فيُحفظ سبب مثل '); DROP TABLE warnings; -- كنص عادي. لا تبنِ استعلام SQL أبدًا باستخدام f-string.

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 من الصف غير المؤكَّد. في اختبارنا لم يرَ أي اتصال آخر (0 اتصالات) صفًّا أُدخل في مجمّع بدون autocommit=True. إما أن تمرّر 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

هل تحتاج إلى خادم بوت مع قاعدة بيانات مشمولة؟ اطّلع على باقات البوتات.

بوتك مستضاف في مكان آخر بالفعل؟ انقله إلينا

أحدث مقالات المدونة