NIEUW 📢 Beperkte lanceeractie: tot 70% korting op je eerste maand, voor elke game!Bekijk pakketten

Een MySQL-database gebruiken met een python-telegram-bot-bot (aiomysql)

Telegram-bothostingUpdated Nov 6, 202612 min read

Kort antwoord. Voeg aiomysql toe aan je python-telegram-bot-bot, open één pool met autocommit=True in post_init, bewaar hem in application.bot_data zodat elke handler erbij kan, en sluit hem in post_shutdown. Gebruik %s-placeholders en bewaar Telegram-ID's als BIGINT, niet als INT. Bij PlayNHost zit in elk botplan één MySQL-database: maak hem aan op de pagina Databases, plak de gegevens in het paneel Omgeving en de code hieronder werkt zoals hij is. De database accepteert verbindingen van je PlayNHost-servers en van phpMyAdmin, niet van je eigen computer. We hebben deze code getest op 6 november 2026 met python-telegram-bot 22.8, aiomysql 0.3.2 en Python 3.12, tegen MariaDB 10.11 met een databaseaccount dat is ingesteld zoals het onze (20 verbindingen, querylimiet van 120 seconden) en een vervanger voor de Bot API-server van Telegram: 8 commando's erin, 8 antwoorden eruit, de notities opgeslagen in de database. Een echte Telegram-login was geen onderdeel van de test.

Wat je nodig hebt

1. De database aanmaken

  1. Open je botserver in het paneel en klik op Databases.
  2. Klik op Nieuwe database. Een Weergavenaam is optioneel; Wordt aangemaakt als toont de volledige naam. Klik op Database aanmaken.
  3. De pagina van de nieuwe database opent (later klik je hem aan bij Jouw databases). De kaart Verbinding toont Host, Poort, Database, Gebruikersnaam en Wachtwoord. Onder Kant-en-klare config heeft Config voor een Python (aiomysql)-snippet waarin dezelfde waarden al zijn ingevuld.

2. De gegevens in Omgeving zetten

  1. Open de pagina Bot, het paneel Omgeving, en klik op Een .env-bestand plakken.
  2. Plak deze vijf regels met de waarden van de kaart Verbinding:
    DB_HOST=your-host
    DB_PORT=3306
    DB_NAME=your-database
    DB_USER=your-username
    DB_PASSWORD=your-password
  3. DB_PASSWORD krijgt een badge Geheim: de waarde wordt alleen schrijfbaar opgeslagen en nooit meer getoond. Klik op 5 variabelen importeren.

Gebruik de echte poort van de kaart Verbinding. De bottoken staat niet in deze regels: de token die je hebt opgeslagen op de pagina Bot, paneel Telegram, komt al in je code aan als TELEGRAM_BOT_TOKEN, de naam die de code hieronder leest (en als BOT_TOKEN, TOKEN, TELEGRAM_TOKEN en TELOXIDE_TOKEN). Staat er in je .env-bestand een tokenregel, dan slaat het dialoogvenster die over. Meer over dit paneel: Omgevingsvariabelen en geheimen instellen voor je Discord-bot.

3. De code

Een kleine notitiebot: /note buy milk slaat een notitie op, /notes toont je laatste vijf.

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()

Wat hij doet: run_polling() vraagt eerst aan Telegram wie de bot is, voert dan post_init uit en begint pas daarna berichten op te halen. post_init opent de pool, maakt de tabel notes aan als die ontbreekt, zet de pool in application.bot_data["pool"] en print Database ready en Logged in as @your_bot. Elke handler krijgt hetzelfde dictionary als context.bot_data. Als de bot stopt, voert run_polling() de functie post_shutdown uit, die de pool sluit.

context.args is de tekst na het commando, in woorden gesplitst, dus de code voegt hem weer samen met enkele spaties: regeleinden en dubbele spaties in een notitie worden één spatie. Heb je de notitie precies zoals getypt nodig, neem dan update.effective_message.text en knip het commando er zelf af.

De %s-placeholders worden door de driver ingevuld, dus een notitie als '); DROP TABLE notes; -- wordt als gewone tekst opgeslagen. In onze test werd ze opgeslagen als rij 2 en bleef de tabel bestaan. Bouw de SQL nooit op met een f-string.

Telegram-ID's opslaan als BIGINT

In de Bot API-documentatie van Telegram staat dat een gebruikers- of chat-ID „meer dan 32 significante bits kan hebben” maar er hooguit 52 heeft, dus een 64-bits integer is veilig. De pagina over ID's geeft de bereiken: gebruikers-ID's gaan tot 1.099.511.627.775, terwijl ID's van groepen, supergroepen en kanalen negatief zijn. Supergroepen en kanalen lopen van -1.000.000.000.001 tot -1.997.852.516.352 (bijvoorbeeld -1001234567890). De INT van MySQL stopt bij 2.147.483.647. In onze test, met dezelfde strikte SQL-modus die onze databaseserver gebruikt, mislukte het opslaan van gebruikers-ID 7.000.000.001 in een INT-kolom. aiomysql gaf:

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

Een BIGINT-kolom sloeg hem wel op, en -1001234567890 ook. Gebruik gewoon BIGINT, niet BIGINT UNSIGNED, zodat ook chat-ID's passen.

4. Starten maar

  1. Upload bot.py en requirements.txt bij Files.
  2. Druk op Starten en open Console. Bij het starten worden de pakketten uit requirements.txt geïnstalleerd, daarna zie je [PlayNHost] Starting your bot, Database ready en Logged in as @your_bot. Die laatste regel zet de status op Overzicht op Online in Telegram.
  3. Stuur je bot in Telegram /note buy milk en daarna /notes. Om de rijen te bekijken, open je de pagina van de database en klik je op Openen in phpMyAdmin.

Vanaf de tweede start toont de console ook Warning: Table 'notes' already exists, met het pad van cursors.py van aiomysql ervoor. Dat is de melding van MariaDB voor CREATE TABLE IF NOT EXISTS, doorgegeven door de driver. Er is niets mis.

In onze test stuurde de vervanger van Telegram deze commando's en kreeg hij deze antwoorden:

VerstuurdAntwoord
/note buy milkSaved. Send /notes to see your last five.
/noteWrite your note after the command, for example: /note buy milk
/note en 501 tekensA note can be up to 500 characters.
/notesDe opgeslagen notities, nieuwste eerst
/notes van een tweede gebruikerYou have no notes yet.

Eén update tegelijk

Met de standaardinstellingen verwerkt python-telegram-bot één update tegelijk: het volgende commando wacht tot de vorige handler klaar is. In onze test kregen 4 commando's die tegelijk binnenkwamen antwoord in de volgorde waarin ze binnenkwamen, en na de test had de bot 1 open verbinding. maxsize=5 blijft belangrijk als je concurrent_updates aanzet in Application.builder(), of als jobs en handlers tegelijk querien: de pool opent nooit meer dan 5 verbindingen en een extra query wacht op een vrije.

De autocommit-valkuil

aiomysql opent verbindingen met autocommit uit. Een INSERT blijft dan in een open transactie staan. Als de verbinding met die nog open transactie terug naar de pool gaat, sluit aiomysql hem en gooit MariaDB de niet-bevestigde rij weg. In onze test werd een rij die op een pool zonder autocommit=True was ingevoegd door 0 andere verbindingen gezien. Geef autocommit=True mee aan create_pool, zoals hierboven, of roep await conn.commit() aan na je schrijfacties.

Een mislukte query stopt de bot niet

In onze test verwijderden we de tabel terwijl de bot draaide en stuurden we /notes. python-telegram-bot printte No error handlers are registered, logging exception. en een traceback die eindigde op pymysql.err.ProgrammingError: (1146, "Table '...notes' doesn't exist"), stuurde geen antwoord op dat commando en bleef draaien: zodra de tabel terug was, werd de volgende /note opgeslagen. Wie het commando stuurde, ziet niets, dus vang de fout op in de handler, of registreer een handler met application.add_error_handler(), en antwoord met een kort bericht.

Een rustige nacht breekt de pool niet

Onze databaseserver sluit een verbinding die 8 uur ongebruikt is geweest. Een bot die zo lang geen berichten krijgt, bijvoorbeeld 's nachts, treft zijn gepoolde verbindingen gesloten aan. aiomysql controleert dat als een handler een verbinding vraagt en opent een nieuwe in plaats van een gesloten verbinding. We testten het met de inactiviteitslimiet verlaagd naar 5 seconden: na 7 rustige seconden werd de volgende /note zonder fout opgeslagen.

Limieten die voor je pool belangrijk zijn

  • 20 verbindingen tegelijk per databasegebruiker. Elk proces dat dezelfde login gebruikt telt mee. Verbinding 21 wordt geweigerd met (1226, "User '...' has exceeded the 'max_user_connections' resource (current value: 20)"). maxsize=5 houdt de bot ruim eronder.
  • Een query die langer dan 120 seconden duurt, wordt gestopt. MariaDB beëindigt hem met fout 1969, Query execution was interrupted (max_statement_time exceeded). In een eerdere test van ons, op een account dat op dezelfde manier is ingesteld, gebeurde dat na 120,03 seconden. De queries van een bot duren normaal milliseconden, dus dit raakt alleen een op hol geslagen query.
  • 500 MB per database. De pagina Databases toont hoeveel er gebruikt is. Bij 500 MB worden schrijfacties gestopt tot je rijen verwijdert.
  • Serverback-ups bevatten de database niet. Exporteer hem af en toe vanuit phpMyAdmin.

Foutmeldingen en wat ze betekenen

Dit zijn de fouten die aiomysql in onze test gaf, geschreven zoals ze een traceback in de console afsluiten (ze komen van PyMySQL, dat aiomysql eronder gebruikt):

  • pymysql.err.OperationalError: (1045, "Access denied for user ..."): verkeerde DB_USER of DB_PASSWORD. Na Wachtwoord resetten op de kaart Verbinding werkt het oude wachtwoord meteen niet meer: vervang DB_PASSWORD en herstart de bot.
  • pymysql.err.OperationalError: (2003, "Can't connect to MySQL server on ..."): verkeerde host of poort, of een verbinding van buiten PlayNHost. De database accepteert alleen verbindingen van PlayNHost-servers en phpMyAdmin, dus dit krijg je ook op je eigen computer.
  • pymysql.err.OperationalError: (1226, ... 'max_user_connections' ...): te veel open verbindingen, zie de limieten hierboven. Bijvoorbeeld een tweede kopie van de bot, of een pool die voor elk bericht wordt geopend.
  • pymysql.err.DataError: (1264, "Out of range value for column 'user_id' at row 1"): een ID-kolom is INT. Verander hem in BIGINT.
  • pymysql.err.ProgrammingError: (1146, "Table '...' doesn't exist"): de tabel ontbreekt. De code hierboven maakt hem aan bij het starten; heb je hem met de hand gemaakt, controleer dan de naam.

Een variabele die ontbreekt in Omgeving stopt de bot voordat hij verbindt, met KeyError: 'DB_HOST' of de naam van de ontbrekende, omdat de code elke waarde leest met os.environ[...]. Voeg hem toe en herstart de bot: variabelen gelden bij de volgende start.

Een verkeerde token stopt de bot vóór het databasedeel, omdat post_init pas draait nadat Telegram de token heeft geaccepteerd. Met onze nepttoken tegen het echte Telegram printte de bot nooit Database ready en stopte hij na ongeveer 1 seconde met telegram.error.InvalidToken: The token `...` was rejected by the server. Dat bericht bevat de hele token, dus plak consoleoutput daarmee nergens openbaar. Kopieer de token opnieuw van @BotFather en plak hem op de pagina Bot, paneel Telegram. Meer Telegram-fouten: Telegram-bot 409 Conflict oplossen.

Hoeveel geheugen hij gebruikt

In onze test gebruikte de draaiende bot 48,5 MB geheugen (resident) na het beantwoorden van de 8 commando's, met open pool, in een container met een limiet van 512 MB zoals ons kleinste botplan. In onze eerdere hostingtest mat python-telegram-bot alleen 44 MB (die bot stopte bij een geweigerde token, voordat hij een bericht verwerkte), dus het verschil, ongeveer 4,5 MB, dekt aiomysql, de pool en de verwerkte commando's. Het installeren van python-telegram-bot en aiomysql met pip vanaf nul piekte op 73 MB, bestandscache inbegrepen. De bibliotheken naast elkaar: python-telegram-bot vs aiogram op PlayNHost.

Hetzelfde met een andere bibliotheek

Een server voor je Telegram-bot met database inbegrepen nodig? Bekijk de Telegram-botplannen.

Draait je bot al ergens anders? Verhuis hem naar ons

ONZE NIEUWSTE BLOGS