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

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

استضافة بوتات TelegramUpdated Nov 6, 202611 min read

الجواب المختصر. أضِف aiomysql إلى بوت aiogram 3 الخاص بك، وافتح مجمّع اتصالات (pool) واحداً مع autocommit=True قبل بدء الاستطلاع (polling)، ثم مرّره إلى المعالجات (handlers) عبر dp.start_polling(bot, pool=pool): سيمرّره aiogram إلى كل معالج لديه معامل pool. استخدم العناصر النائبة %s وخزّن معرّفات Telegram بنوع BIGINT لا INT. في PlayNHost تتضمن كل خطة بوت قاعدة بيانات MySQL واحدة: أنشئها في صفحة قواعد البيانات، والصق تفاصيلها في جزء البيئة وسيعمل الكود أدناه كما هو. تقبل القاعدة الاتصالات من خوادم PlayNHost الخاصة بك ومن phpMyAdmin، لا من حاسوبك الشخصي. اختبرنا هذا الكود في 6 نوفمبر 2026 مع aiogram 3.31.0 و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، يصل إلى كودك أصلاً باسم BOT_TOKEN وهو الاسم الذي تستخدمه أمثلة aiogram نفسها (وكذلك باسم TELEGRAM_BOT_TOKEN، TOKEN، TELEGRAM_TOKEN وTELOXIDE_TOKEN). وإذا كان ملف .env يحتوي على سطر رمز، يتخطاه مربع الحوار. المزيد عن هذا الجزء: كيف تضبط متغيرات البيئة والأسرار لبوت Discord.

3. الكود

بوت ملاحظات صغير: /note buy milk يحفظ ملاحظة، و/notes يعرض آخر خمس ملاحظات لك.

requirements.txt

aiogram>=3,<4
aiomysql

bot.py

import asyncio
import logging
import os
import sys

import aiomysql
from aiogram import Bot, Dispatcher
from aiogram.filters import Command, CommandObject
from aiogram.types import Message

dp = Dispatcher()


@dp.message(Command("note"))
async def note(message: Message, command: CommandObject, pool: aiomysql.Pool) -> None:
    text = (command.args or "").strip()
    if not text:
        await message.answer("Write your note after the command, for example: /note buy milk")
        return
    if len(text) > 500:
        await message.answer("A note can be up to 500 characters.")
        return
    async with pool.acquire() as conn:
        async with conn.cursor() as cur:
            await cur.execute(
                "INSERT INTO notes (user_id, text) VALUES (%s, %s)",
                (message.from_user.id, text),
            )
    await message.answer("Saved. Send /notes to see your last five.")


@dp.message(Command("notes"))
async def notes(message: Message, pool: aiomysql.Pool) -> None:
    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",
                (message.from_user.id,),
            )
            rows = await cur.fetchall()
    if not rows:
        await message.answer("You have no notes yet.")
        return
    await message.answer("\n".join(f"- {text}" for (text,) in rows))


async def main() -> 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))"
            )
    print("Database ready")

    bot = Bot(os.environ["BOT_TOKEN"])
    try:
        # Every keyword passed here reaches the handlers that ask for it by name.
        await dp.start_polling(bot, pool=pool)
    finally:
        pool.close()
        await pool.wait_closed()


if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO, stream=sys.stdout)
    asyncio.run(main())

ماذا يفعل: الدالة main() تفتح المجمّع وتنشئ جدول notes إن كان مفقوداً وتطبع Database ready قبل أن يطلب البوت الرسائل من Telegram. dp.start_polling(bot, pool=pool) هي طريقة aiogram الخاصة لمشاركة كائن: كل وسيط مسمّى إضافي تمرّره يصبح بيانات يسلّمها aiogram لأي معالج لديه معامل بهذا الاسم، لذلك تحصل note() وnotes() على المجمّع نفسه. وعندما يتوقف البوت، تغلق الكتلة finally المجمّع.

يملأ المشغّل (driver) العناصر النائبة %s، لذلك تُخزَّن ملاحظة مثل '); DROP TABLE notes; -- كنص عادي. في اختبارنا حُفظت في الصف 2 وبقي الجدول. لا تبنِ استعلام SQL أبداً بـ f-string.

خزّن معرّفات Telegram بنوع BIGINT

تقول وثائق Bot API الخاصة بـ Telegram إن معرّف المستخدم أو المحادثة «قد يحتوي على أكثر من 32 بتاً معنوياً» لكنه لا يتجاوز 52، لذا فالعدد الصحيح ذو 64 بت آمن. وتعطي صفحتها عن المعرّفات النطاقات: تصل معرّفات المستخدمين إلى 1,099,511,627,775، بينما معرّفات المجموعات والمجموعات الخارقة والقنوات سالبة. وتمتد المجموعات الخارقة والقنوات من -1,000,000,000,001 إلى -1,997,852,516,352 (مثلاً -1001234567890). أما INT في MySQL فيتوقف عند 2,147,483,647. في اختبارنا، وبنفس وضع SQL الصارم الذي يستخدمه خادم قاعدة بياناتنا، فشل حفظ معرّف المستخدم 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، حتى تتسع معرّفات المحادثات أيضاً.

4. شغّله

  1. ارفع bot.py وrequirements.txt في الملفات.
  2. اضغط تشغيل وافتح وحدة التحكم. يثبّت التشغيل الحزم من requirements.txt ثم يعرض [PlayNHost] Starting your bot، Database ready وRun polling for bot @your_bot. وهذا السطر الأخير يحوّل الحالة في نظرة عامة إلى متصل في Telegram.
  3. في Telegram، أرسل إلى بوتك /note buy milk ثم /notes. ولعرض الصفوف، افتح صفحة القاعدة وانقر على فتح في phpMyAdmin.

ومنذ التشغيل الثاني تعرض وحدة التحكم أيضاً Warning: Table 'notes' already exists، ويسبقه مسار الملف cursors.py الخاص بـ aiomysql. وهذه ملاحظة من MariaDB بخصوص CREATE TABLE IF NOT EXISTS، ينقلها المشغّل (driver). لا يوجد خطأ.

في اختبارنا أرسل بديل 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.

رسائل كثيرة في وقت واحد

يعالج aiogram كل تحديث وارد كمهمة مستقلة، لذلك يمكن لعدة أوامر تنفيذ استعلاماتها في الوقت نفسه. في اختبارنا عولجت 4 أوامر وصلت معاً، كلٌّ منها في نحو 50 مللي ثانية. maxsize=5 يحدّ المجمّع بـ 5 اتصالات؛ ويقف الاستعلام السادس بانتظار اتصال حرّ بدلاً من فتح اتصال آخر. بعد الاختبار كان لدى البوت 2 اتصال مفتوح.

فخ autocommit

يفتح aiomysql الاتصالات مع إيقاف autocommit. عندها يبقى أمر INSERT داخل معاملة مفتوحة. وعندما يعود الاتصال إلى المجمّع وما زالت المعاملة مفتوحة، يغلقه aiomysql، فتتخلص MariaDB من الصف غير المثبَّت. في اختبارنا، صف أُدرج في مجمّع دون autocommit=True رآه 0 اتصال آخر. إمّا مرّر autocommit=True إلى create_pool، كما في الأعلى، أو استدعِ await conn.commit() بعد عمليات الكتابة.

فشل استعلام لا يوقف البوت

في اختبارنا حذفنا الجدول والبوت يعمل، وأرسلنا /notes. سجّل aiogram الخطأ مع traceback ينتهي بـ pymysql.err.ProgrammingError: (1146, "Table '...notes' doesn't exist")، ولم يرسل أي رد على ذلك الأمر وواصل العمل: بعد عودة الجدول حُفظ أمر /note التالي. من أرسل الأمر لا يرى شيئاً، لذا إن كان من الممكن أن يفشل استعلام فاعترض الخطأ في المعالج وردّ برسالة قصيرة.

ليلة هادئة لا تكسر المجمّع

يغلق خادم قاعدة بياناتنا أي اتصال خامل لمدة 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 في اختبارنا، مكتوبة كما تنتهي بها الـ traceback في وحدة التحكم (وهي قادمة من 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"): عمود المعرّف من نوع INT. غيّره إلى BIGINT.
  • pymysql.err.ProgrammingError: (1146, "Table '...' doesn't exist"): الجدول مفقود. ينشئه الكود أعلاه عند البدء؛ وإن أنشأته يدوياً فتحقق من اسمه.

متغير مفقود من البيئة يوقف البوت قبل أن يتصل، مع KeyError: 'DB_HOST' أو اسم المتغير المفقود، لأن الكود يقرأ كل قيمة بـ os.environ[...]. أضفه ثم أعد تشغيل البوت: تُطبَّق المتغيرات عند التشغيل التالي.

يظهر الرمز الخاطئ بعد جزء قاعدة البيانات. مع رمزنا المزيف أمام Telegram الحقيقي، طبع البوت Database ready، ثم توقف مع aiogram.exceptions.TelegramUnauthorizedError: Telegram server says - Unauthorized. انسخ الرمز مجدداً من @BotFather والصقه في صفحة بوت، جزء Telegram. مزيد من أخطاء Telegram: إصلاح خطأ 409 Conflict في بوت Telegram.

كم يستهلك من الذاكرة

في اختبارنا استهلك البوت العامل 174 MB من الذاكرة (resident) بعد أن ردّ على الأوامر الـ 8، مع بقاء مجمّعه مفتوحاً، داخل حاوية محدودة بـ 512 MB مثل أصغر خطة بوت لدينا. ومعظم ذلك هو aiogram نفسه: ففي اختبار الاستضافة السابق لدينا قاس aiogram وحده 173 MB (توقف ذلك البوت عند رمز مرفوض، قبل معالجة أي رسالة، لذا فالرقمان متقاربان لكنها ليست مقارنة دقيقة). وبلغ تثبيت aiogram وaiomysql بـ pip من الصفر ذروة 163 MB، مع ذاكرة الملفات المؤقتة. وهذا يترك مجالاً لكودك على أصغر خطة. ويقارن دليل الاستضافة بين المكتبات: python-telegram-bot مقابل aiogram على PlayNHost.

الأمر نفسه مع مكتبة أخرى

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

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

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