NOUVEAU 📢 Offre de lancement limitée : jusqu'à 70 % de remise sur votre premier mois, pour tous les jeux !Voir les offres

Comment utiliser une base de données MySQL avec un bot python-telegram-bot (aiomysql)

Hébergement de bots TelegramUpdated Nov 6, 202613 min read

En bref. Ajoutez aiomysql à votre bot python-telegram-bot, ouvrez un seul pool avec autocommit=True dans post_init, gardez-le dans application.bot_data pour que chaque handler puisse y accéder, et fermez-le dans post_shutdown. Utilisez des paramètres %s et stockez les IDs Telegram en BIGINT, pas en INT. Chez PlayNHost, chaque offre de bot inclut une base de données MySQL : créez-la sur la page Bases de données, collez ses informations dans le volet Environnement, et le code ci-dessous fonctionne tel quel. La base accepte les connexions de vos serveurs PlayNHost et de phpMyAdmin, pas celles de votre propre ordinateur. Nous avons testé ce code le 6 novembre 2026 avec python-telegram-bot 22.8, aiomysql 0.3.2 et Python 3.12, sur MariaDB 10.11 avec un compte de base de données configuré comme les nôtres (20 connexions, limite de requête de 120 secondes) et un substitut du serveur Bot API de Telegram : 8 commandes envoyées, 8 réponses reçues, les notes enregistrées dans la base. Une vraie connexion à Telegram ne faisait pas partie du test.

Ce dont vous avez besoin

1. Créer la base de données

  1. Ouvrez votre serveur de bot dans le panel et cliquez sur Bases de données.
  2. Cliquez sur Nouvelle base de données. Un Libellé est facultatif ; Sera créée sous le nom affiche le nom complet. Cliquez sur Créer une base de données.
  3. La page de la nouvelle base s'ouvre (plus tard, cliquez dessus dans Vos bases de données). Sa carte Connexion affiche Hôte, Port, Base de données, Nom d'utilisateur et Mot de passe. Sous Config prête à l’emploi, Config pour propose un extrait Python (aiomysql) avec les mêmes valeurs déjà remplies.

2. Mettre les informations dans Environnement

  1. Ouvrez la page Bot, le volet Environnement, et cliquez sur Coller un fichier .env.
  2. Collez ces cinq lignes avec les valeurs de la carte Connexion :
    DB_HOST=your-host
    DB_PORT=3306
    DB_NAME=your-database
    DB_USER=your-username
    DB_PASSWORD=your-password
  3. DB_PASSWORD reçoit un badge Secret : il est enregistré en écriture seule et n'est plus jamais affiché. Cliquez sur Importer 5 variables.

Utilisez le vrai port de la carte Connexion. Le token du bot ne fait pas partie de ces lignes : le token que vous avez enregistré sur la page Bot, volet Telegram, arrive déjà dans votre code sous le nom TELEGRAM_BOT_TOKEN, celui que lit le code ci-dessous (et sous BOT_TOKEN, TOKEN, TELEGRAM_TOKEN et TELOXIDE_TOKEN). Si votre fichier .env contient une ligne de token, la boîte de dialogue l'ignore. Plus d'infos sur ce volet : Comment définir des variables d’environnement et des secrets pour votre bot Discord.

3. Le code

Un petit bot de notes : /note buy milk enregistre une note, /notes affiche vos cinq dernières.

requirements.txt

python-telegram-bot>=22,<23
aiomysql

bot.py

import os

import aiomysql
from telegram import Update
from telegram.ext import Application, CommandHandler, ContextTypes


async def post_init(application: Application) -> 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))"
            )
    application.bot_data["pool"] = pool
    print("Database ready")
    print(f"Logged in as @{application.bot.username}")


async def post_shutdown(application: Application) -> None:
    pool = application.bot_data.get("pool")
    if pool is not None:
        pool.close()
        await pool.wait_closed()


async def note(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    text = " ".join(context.args).strip()
    if not text:
        await update.effective_message.reply_text("Write your note after the command, for example: /note buy milk")
        return
    if len(text) > 500:
        await update.effective_message.reply_text("A note can be up to 500 characters.")
        return
    pool = context.bot_data["pool"]
    async with pool.acquire() as conn:
        async with conn.cursor() as cur:
            await cur.execute(
                "INSERT INTO notes (user_id, text) VALUES (%s, %s)",
                (update.effective_user.id, text),
            )
    await update.effective_message.reply_text("Saved. Send /notes to see your last five.")


async def notes(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    pool = context.bot_data["pool"]
    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",
                (update.effective_user.id,),
            )
            rows = await cur.fetchall()
    if not rows:
        await update.effective_message.reply_text("You have no notes yet.")
        return
    await update.effective_message.reply_text("\n".join(f"- {text}" for (text,) in rows))


def main() -> None:
    application = (
        Application.builder()
        .token(os.environ["TELEGRAM_BOT_TOKEN"])
        .post_init(post_init)
        .post_shutdown(post_shutdown)
        .build()
    )
    application.add_handler(CommandHandler("note", note))
    application.add_handler(CommandHandler("notes", notes))
    application.run_polling()


if __name__ == "__main__":
    main()

Ce qu'il fait : run_polling() demande d'abord à Telegram qui est le bot, puis exécute post_init, et ne commence qu'ensuite à récupérer les messages. post_init ouvre le pool, crée la table notes si elle n'existe pas, place le pool dans application.bot_data["pool"] et affiche Database ready et Logged in as @your_bot. Chaque handler reçoit le même dictionnaire sous le nom context.bot_data. Quand le bot s'arrête, run_polling() exécute post_shutdown, qui ferme le pool.

context.args est le texte qui suit la commande, découpé en mots ; le code le recolle donc avec des espaces simples : les sauts de ligne et les doubles espaces dans une note deviennent un seul espace. Si vous avez besoin de la note exactement telle qu'elle a été saisie, prenez update.effective_message.text et retirez la commande vous-même.

Les paramètres %s sont remplis par le driver, donc une note comme '); DROP TABLE notes; -- est enregistrée comme du simple texte. Dans notre test, elle a été enregistrée comme ligne 2 et la table est restée. Ne construisez jamais le SQL avec une f-string.

Stocker les IDs Telegram en BIGINT

La documentation de la Bot API de Telegram indique qu'un ID d'utilisateur ou de chat « peut avoir plus de 32 bits significatifs » mais en a au plus 52, donc un entier de 64 bits suffit. Sa page sur les IDs donne les plages : les IDs d'utilisateur montent jusqu'à 1.099.511.627.775, tandis que les IDs de groupes, de supergroupes et de canaux sont négatifs. Les supergroupes et les canaux vont de -1.000.000.000.001 à -1.997.852.516.352 (par exemple -1001234567890). Le INT de MySQL s'arrête à 2.147.483.647. Dans notre test, avec le même mode SQL strict que celui de notre serveur de base de données, l'enregistrement de l'ID d'utilisateur 7.000.000.001 dans une colonne INT a échoué. aiomysql a levé :

pymysql.err.DataError: (1264, "Out of range value for column 'user_id' at row 1")

Une colonne BIGINT l'a stocké, ainsi que -1001234567890. Utilisez simplement BIGINT, pas BIGINT UNSIGNED, pour que les IDs de chat tiennent aussi.

4. Lancer le bot

  1. Téléversez bot.py et requirements.txt dans Fichiers.
  2. Appuyez sur Démarrer et ouvrez Console. Le démarrage installe les paquets de requirements.txt, puis affiche [PlayNHost] Starting your bot, Database ready et Logged in as @your_bot. Cette dernière ligne fait passer l'état dans Aperçu à En ligne sur Telegram.
  3. Dans Telegram, envoyez à votre bot /note buy milk, puis /notes. Pour voir les lignes, ouvrez la page de la base et cliquez sur Ouvrir dans phpMyAdmin.

À partir du deuxième démarrage, la console affiche aussi Warning: Table 'notes' already exists, précédé du chemin du cursors.py d'aiomysql. C'est la remarque de MariaDB pour CREATE TABLE IF NOT EXISTS, relayée par le driver. Tout va bien.

Dans notre test, le substitut de Telegram a envoyé ces commandes et reçu ces réponses :

EnvoyéRéponse
/note buy milkSaved. Send /notes to see your last five.
/noteWrite your note after the command, for example: /note buy milk
/note et 501 caractèresA note can be up to 500 characters.
/notesLes notes enregistrées, les plus récentes d'abord
/notes d'un deuxième utilisateurYou have no notes yet.

Une mise à jour à la fois

Avec ses réglages par défaut, python-telegram-bot traite une mise à jour à la fois : la commande suivante attend que le handler précédent ait terminé. Dans notre test, 4 commandes arrivées en même temps ont reçu leur réponse dans l'ordre d'arrivée, et après le test le bot détenait 1 connexion ouverte. maxsize=5 reste utile si vous activez concurrent_updates dans Application.builder(), ou si des jobs et des handlers interrogent la base en même temps : le pool n'ouvre jamais plus de 5 connexions, et une requête supplémentaire attend qu'une se libère.

Le piège de l'autocommit

aiomysql ouvre les connexions avec l'autocommit désactivé. Un INSERT reste alors dans une transaction ouverte. Quand la connexion retourne dans le pool avec cette transaction encore ouverte, aiomysql la ferme et MariaDB jette la ligne non validée. Dans notre test, une ligne insérée sur un pool sans autocommit=True n'a été vue par aucune des 0 autres connexions. Soit vous passez autocommit=True à create_pool, comme ci-dessus, soit vous appelez await conn.commit() après vos écritures.

Une requête en échec n'arrête pas le bot

Dans notre test, nous avons supprimé la table pendant que le bot tournait et envoyé /notes. python-telegram-bot a affiché No error handlers are registered, logging exception. et une trace d'erreur se terminant par pymysql.err.ProgrammingError: (1146, "Table '...notes' doesn't exist"), n'a envoyé aucune réponse pour cette commande et a continué de tourner : une fois la table revenue, le /note suivant a été enregistré. La personne qui a envoyé la commande ne voit rien ; interceptez donc l'erreur dans le handler, ou enregistrez un gestionnaire d'erreurs avec application.add_error_handler(), et répondez par un court message.

Une nuit calme ne casse pas le pool

Notre serveur de base de données ferme une connexion restée inactive pendant 8 heures. Un bot qui ne reçoit aucun message aussi longtemps, pendant la nuit par exemple, retrouve ses connexions du pool fermées. aiomysql le vérifie quand un handler demande une connexion et en ouvre une nouvelle à la place de celle qui est fermée. Nous l'avons testé avec la limite d'inactivité abaissée à 5 secondes : après 7 secondes de calme, le /note suivant a été enregistré sans erreur.

Les limites qui comptent pour votre pool

  • 20 connexions simultanées par utilisateur de base de données. Chaque processus qui utilise le même identifiant compte. La connexion 21 est refusée avec (1226, "User '...' has exceeded the 'max_user_connections' resource (current value: 20)"). maxsize=5 garde le bot bien en dessous.
  • Une requête qui dure plus de 120 secondes est arrêtée. MariaDB la termine avec l'erreur 1969, Query execution was interrupted (max_statement_time exceeded). Lors d'un test précédent, sur un compte configuré de la même façon, c'est arrivé après 120,03 secondes. Les requêtes d'un bot prennent normalement quelques millisecondes, donc cela ne touche qu'une requête qui s'emballe.
  • 500 MB par base de données. La page Bases de données indique l'espace utilisé. À 500 MB, les écritures sont bloquées jusqu'à ce que vous supprimiez des lignes.
  • Les sauvegardes du serveur n'incluent pas la base de données. Exportez-la de temps en temps depuis phpMyAdmin.

Les erreurs et leur signification

Voici les erreurs qu'aiomysql a renvoyées dans notre test, écrites comme elles terminent une trace dans la console (elles viennent de PyMySQL, qu'aiomysql utilise en dessous) :

  • pymysql.err.OperationalError: (1045, "Access denied for user ...") : DB_USER ou DB_PASSWORD incorrect. Après Réinitialiser le mot de passe sur la carte Connexion, l'ancien mot de passe cesse de fonctionner immédiatement : remplacez DB_PASSWORD et redémarrez le bot.
  • pymysql.err.OperationalError: (2003, "Can't connect to MySQL server on ...") : hôte ou port incorrect, ou connexion depuis l'extérieur de PlayNHost. La base n'accepte que les connexions des serveurs PlayNHost et de phpMyAdmin, c'est donc aussi ce que vous obtenez depuis votre propre ordinateur.
  • pymysql.err.OperationalError: (1226, ... 'max_user_connections' ...) : trop de connexions ouvertes, voir les limites ci-dessus. Par exemple une deuxième copie du bot, ou un pool ouvert pour chaque message.
  • pymysql.err.DataError: (1264, "Out of range value for column 'user_id' at row 1") : une colonne d'ID est en INT. Passez-la en BIGINT.
  • pymysql.err.ProgrammingError: (1146, "Table '...' doesn't exist") : la table est absente. Le code ci-dessus la crée au démarrage ; si vous l'avez créée à la main, vérifiez son nom.

Une variable manquante dans Environnement arrête le bot avant la connexion, avec KeyError: 'DB_HOST' ou le nom de la variable manquante, car le code lit chaque valeur avec os.environ[...]. Ajoutez-la, puis redémarrez le bot : les variables s'appliquent au prochain démarrage.

Un mauvais token arrête le bot avant la partie base de données, car post_init ne s'exécute qu'après l'acceptation du token par Telegram. Avec notre faux token face au vrai Telegram, le bot n'a jamais affiché Database ready et s'est arrêté après environ 1 seconde avec telegram.error.InvalidToken: The token `...` was rejected by the server. Ce message contient le token en entier, ne collez donc jamais une sortie de console qui le contient dans un lieu public. Recopiez le token depuis @BotFather et collez-le sur la page Bot, volet Telegram. Plus d'erreurs Telegram : Corriger l’erreur 409 Conflict d’un bot Telegram.

Quelle mémoire il utilise

Dans notre test, le bot en marche a utilisé 48,5 MB de mémoire (résidente) après avoir répondu aux 8 commandes, avec son pool ouvert, dans un conteneur plafonné à 512 MB comme notre plus petite offre de bot. Lors de notre test d'hébergement précédent, python-telegram-bot seul mesurait 44 MB (ce bot s'est arrêté sur un token refusé, avant de traiter un message), la différence, environ 4,5 MB, couvre donc aiomysql, le pool et les commandes traitées. L'installation de python-telegram-bot et d'aiomysql avec pip à partir de zéro a atteint un pic de 73 MB, cache de fichiers inclus. Les bibliothèques côte à côte : python-telegram-bot vs aiogram sur PlayNHost.

La même chose avec une autre bibliothèque

Besoin d'un serveur pour votre bot Telegram avec une base de données incluse ? Découvrez les offres de bots Telegram.

Votre bot est hébergé ailleurs ? Transférez-le chez nous

NOS DERNIERS ARTICLES