NEU 📢 Begrenztes Startangebot: bis zu 70 % Rabatt auf deinen ersten Monat, für jedes Spiel!Tarife ansehen

So nutzt du eine MySQL-Datenbank mit einem python-telegram-bot-Bot (aiomysql)

Telegram-Bot-HostingUpdated Nov 6, 202612 min read

Kurz gesagt. Füge aiomysql zu deinem python-telegram-bot-Bot hinzu, öffne einen einzigen Pool mit autocommit=True in post_init, lege ihn in application.bot_data ab, damit jeder Handler darankommt, und schließe ihn in post_shutdown. Nutze %s-Platzhalter und speichere Telegram-IDs als BIGINT, nicht als INT. Bei PlayNHost enthält jeder Bot-Tarif eine MySQL-Datenbank: Lege sie auf der Seite Datenbanken an, füge ihre Zugangsdaten im Bereich Umgebung ein, und der Code unten läuft so, wie er ist. Die Datenbank nimmt Verbindungen von deinen PlayNHost-Servern und von phpMyAdmin an, nicht von deinem eigenen Rechner. Wir haben diesen Code am 6. November 2026 getestet, mit python-telegram-bot 22.8, aiomysql 0.3.2 und Python 3.12, gegen MariaDB 10.11 mit einem Datenbankkonto wie unserem (20 Verbindungen, 120 Sekunden Abfragelimit) und einem Ersatz für den Bot-API-Server von Telegram: 8 Befehle hinein, 8 Antworten heraus, die Notizen in der Datenbank gespeichert. Eine echte Telegram-Anmeldung war nicht Teil des Tests.

Was du brauchst

1. Die Datenbank erstellen

  1. Öffne deinen Bot-Server im Panel und klicke auf Datenbanken.
  2. Klicke auf Neue Datenbank. Eine Bezeichnung ist optional; unter Wird erstellt als siehst du den vollständigen Namen. Klicke auf Datenbank erstellen.
  3. Die Seite der neuen Datenbank öffnet sich (später klickst du sie unter Deine Datenbanken an). Ihre Karte Verbindung zeigt Host, Port, Datenbank, Benutzername und Passwort. Unter Fertige Config enthält Config für ein Snippet Python (aiomysql), in dem dieselben Werte schon eingetragen sind.

2. Die Zugangsdaten unter Umgebung eintragen

  1. Öffne die Seite Bot, den Bereich Umgebung, und klicke auf Eine .env-Datei einfügen.
  2. Füge diese fünf Zeilen mit den Werten von der Karte Verbindung ein:
    DB_HOST=your-host
    DB_PORT=3306
    DB_NAME=your-database
    DB_USER=your-username
    DB_PASSWORD=your-password
  3. DB_PASSWORD bekommt ein Label Secret: Der Wert wird nur schreibend gespeichert und nie wieder angezeigt. Klicke auf 5 Variablen importieren.

Nimm den echten Port von der Karte Verbindung. Der Bot-Token gehört nicht zu diesen Zeilen: Der Token, den du auf der Seite Bot im Bereich Telegram gespeichert hast, kommt schon als TELEGRAM_BOT_TOKEN in deinem Code an, so heißt der Name, den der Code unten liest (und außerdem als BOT_TOKEN, TOKEN, TELEGRAM_TOKEN und TELOXIDE_TOKEN). Steht in deiner .env-Datei eine Token-Zeile, überspringt der Dialog sie. Mehr zu diesem Bereich: Umgebungsvariablen und Secrets für deinen Discord-Bot setzen.

3. Der Code

Ein kleiner Notizen-Bot: /note buy milk speichert eine Notiz, /notes zeigt deine letzten fünf.

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

Was er macht: run_polling() fragt zuerst Telegram, wer der Bot ist, führt dann post_init aus und beginnt erst danach, Nachrichten abzuholen. post_init öffnet den Pool, legt die Tabelle notes an, falls sie fehlt, legt den Pool in application.bot_data["pool"] ab und gibt Database ready und Logged in as @your_bot aus. Jeder Handler bekommt dasselbe Dictionary als context.bot_data. Wenn der Bot stoppt, führt run_polling() die Funktion post_shutdown aus, die den Pool schließt.

context.args ist der Text nach dem Befehl, in Wörter zerlegt. Der Code setzt ihn deshalb mit einfachen Leerzeichen wieder zusammen: Zeilenumbrüche und doppelte Leerzeichen innerhalb einer Notiz werden zu einem Leerzeichen. Brauchst du die Notiz genau so, wie sie getippt wurde, nimm update.effective_message.text und schneide den Befehl selbst ab.

Die %s-Platzhalter füllt der Treiber aus, deshalb wird eine Notiz wie '); DROP TABLE notes; -- als normaler Text gespeichert. In unserem Test wurde sie als Zeile 2 gespeichert, und die Tabelle blieb erhalten. Baue das SQL nie mit einem f-String zusammen.

Telegram-IDs als BIGINT speichern

Die Bot-API-Dokumentation von Telegram sagt, dass eine Nutzer- oder Chat-ID „mehr als 32 signifikante Bits haben kann“, aber höchstens 52 hat, ein 64-Bit-Integer ist also sicher. Die Seite zu den IDs nennt die Bereiche: Nutzer-IDs gehen bis 1.099.511.627.775, während Gruppen-, Supergruppen- und Kanal-IDs negativ sind. Supergruppen und Kanäle reichen von -1.000.000.000.001 bis -1.997.852.516.352 (zum Beispiel -1001234567890). Das INT von MySQL endet bei 2.147.483.647. In unserem Test, mit demselben strengen SQL-Modus wie auf unserem Datenbankserver, schlug das Speichern der Nutzer-ID 7.000.000.001 in einer INT-Spalte fehl. aiomysql meldete:

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

Eine BIGINT-Spalte hat sie gespeichert, und -1001234567890 ebenfalls. Nimm einfach BIGINT, nicht BIGINT UNSIGNED, damit auch Chat-IDs hineinpassen.

4. Starten

  1. Lade bot.py und requirements.txt unter Dateien hoch.
  2. Drücke Starten und öffne die Konsole. Beim Start werden die Pakete aus requirements.txt installiert, dann erscheinen [PlayNHost] Starting your bot, Database ready und Logged in as @your_bot. Diese letzte Zeile schaltet den Status unter Übersicht auf Online in Telegram.
  3. Schicke deinem Bot in Telegram /note buy milk und dann /notes. Um dir die Zeilen anzusehen, öffne die Seite der Datenbank und klicke auf In phpMyAdmin öffnen.

Ab dem zweiten Start zeigt die Konsole außerdem Warning: Table 'notes' already exists, davor der Pfad zur cursors.py von aiomysql. Das ist der Hinweis von MariaDB zu CREATE TABLE IF NOT EXISTS, vom Treiber weitergereicht. Es ist nichts kaputt.

In unserem Test hat der Telegram-Ersatz diese Befehle geschickt und diese Antworten bekommen:

GesendetAntwort
/note buy milkSaved. Send /notes to see your last five.
/noteWrite your note after the command, for example: /note buy milk
/note und 501 ZeichenA note can be up to 500 characters.
/notesDie gespeicherten Notizen, die neueste zuerst
/notes von einem zweiten NutzerYou have no notes yet.

Ein Update nach dem anderen

Mit den Standardeinstellungen verarbeitet python-telegram-bot ein Update nach dem anderen: Der nächste Befehl wartet, bis der vorherige Handler fertig ist. In unserem Test wurden 4 Befehle, die gleichzeitig eintrafen, in der Reihenfolge ihres Eintreffens beantwortet, und nach dem Test hielt der Bot 1 offene Verbindung. maxsize=5 ist trotzdem wichtig, wenn du concurrent_updates in Application.builder() einschaltest oder wenn Jobs und Handler gleichzeitig abfragen: Der Pool öffnet nie mehr als 5 Verbindungen, und eine zusätzliche Abfrage wartet auf eine freie.

Die Autocommit-Falle

aiomysql öffnet Verbindungen mit ausgeschaltetem Autocommit. Ein INSERT bleibt dann in einer offenen Transaktion. Geht die Verbindung mit dieser noch offenen Transaktion zurück in den Pool, schließt aiomysql sie, und MariaDB verwirft die nicht bestätigte Zeile. In unserem Test sahen 0 andere Verbindungen eine Zeile, die auf einem Pool ohne autocommit=True eingefügt wurde. Übergib entweder autocommit=True an create_pool, wie oben, oder rufe nach deinen Schreibzugriffen await conn.commit() auf.

Eine fehlgeschlagene Abfrage stoppt den Bot nicht

In unserem Test haben wir die Tabelle gelöscht, während der Bot lief, und /notes gesendet. python-telegram-bot gab No error handlers are registered, logging exception. aus, dazu einen Traceback, der mit pymysql.err.ProgrammingError: (1146, "Table '...notes' doesn't exist") endete, schickte auf diesen Befehl keine Antwort und lief weiter: Sobald die Tabelle wieder da war, wurde das nächste /note gespeichert. Wer den Befehl geschickt hat, sieht nichts. Fange den Fehler deshalb im Handler ab oder registriere mit application.add_error_handler() einen Fehler-Handler und antworte mit einer kurzen Nachricht.

Eine ruhige Nacht macht den Pool nicht kaputt

Unser Datenbankserver schließt eine Verbindung, die 8 Stunden untätig war. Ein Bot, der so lange keine Nachrichten bekommt, zum Beispiel über Nacht, findet seine gepoolten Verbindungen geschlossen vor. aiomysql prüft das, wenn ein Handler eine Verbindung anfordert, und öffnet statt der geschlossenen eine neue. Wir haben es mit einem auf 5 Sekunden gesenkten Leerlauflimit getestet: Nach 7 ruhigen Sekunden wurde das nächste /note ohne Fehler gespeichert.

Limits, die für deinen Pool wichtig sind

  • 20 Verbindungen gleichzeitig pro Datenbankbenutzer. Jeder Prozess mit demselben Login zählt mit. Verbindung 21 wird abgelehnt mit (1226, "User '...' has exceeded the 'max_user_connections' resource (current value: 20)"). maxsize=5 hält den Bot deutlich darunter.
  • Eine Abfrage, die länger als 120 Sekunden läuft, wird gestoppt. MariaDB beendet sie mit dem Fehler 1969, Query execution was interrupted (max_statement_time exceeded). In einem früheren Test von uns, auf einem gleich eingerichteten Konto, kam das nach 120,03 Sekunden. Die Abfragen eines Bots dauern normalerweise Millisekunden, das trifft also nur eine außer Kontrolle geratene Abfrage.
  • 500 MB pro Datenbank. Die Seite Datenbanken zeigt, wie viel belegt ist. Bei 500 MB werden Schreibzugriffe gestoppt, bis du Zeilen löschst.
  • Server-Backups enthalten die Datenbank nicht. Exportiere sie von Zeit zu Zeit aus phpMyAdmin.

Fehler und was sie bedeuten

Das sind die Fehler, die aiomysql in unserem Test gemeldet hat, so geschrieben, wie sie einen Traceback in der Konsole beenden (sie stammen von PyMySQL, das aiomysql darunter verwendet):

  • pymysql.err.OperationalError: (1045, "Access denied for user ..."): falscher DB_USER oder falsches DB_PASSWORD. Nach Passwort zurücksetzen auf der Karte Verbindung funktioniert das alte Passwort sofort nicht mehr: Ersetze DB_PASSWORD und starte den Bot neu.
  • pymysql.err.OperationalError: (2003, "Can't connect to MySQL server on ..."): falscher Host oder Port oder eine Verbindung von außerhalb von PlayNHost. Die Datenbank nimmt nur Verbindungen von PlayNHost-Servern und von phpMyAdmin an, deshalb bekommst du diesen Fehler auch auf deinem eigenen Rechner.
  • pymysql.err.OperationalError: (1226, ... 'max_user_connections' ...): zu viele offene Verbindungen, siehe die Limits oben. Zum Beispiel eine zweite Kopie des Bots oder ein Pool, der für jede Nachricht geöffnet wird.
  • pymysql.err.DataError: (1264, "Out of range value for column 'user_id' at row 1"): Eine ID-Spalte ist INT. Ändere sie in BIGINT.
  • pymysql.err.ProgrammingError: (1146, "Table '...' doesn't exist"): Die Tabelle fehlt. Der Code oben legt sie beim Start an; wenn du sie von Hand erstellt hast, prüfe ihren Namen.

Eine Variable, die unter Umgebung fehlt, stoppt den Bot vor dem Verbinden, mit KeyError: 'DB_HOST' oder dem Namen der fehlenden Variable, weil der Code jeden Wert mit os.environ[...] liest. Füge sie hinzu und starte den Bot neu: Variablen gelten erst beim nächsten Start.

Ein falscher Token stoppt den Bot vor dem Datenbankteil, weil post_init erst läuft, nachdem Telegram den Token akzeptiert hat. Mit unserem erfundenen Token gegen das echte Telegram gab der Bot nie Database ready aus und stoppte nach etwa 1 Sekunde mit telegram.error.InvalidToken: The token `...` was rejected by the server. Diese Meldung enthält den ganzen Token, also füge Konsolenausgaben damit nirgends öffentlich ein. Kopiere den Token erneut von @BotFather und füge ihn auf der Seite Bot im Bereich Telegram ein. Mehr Telegram-Fehler: Telegram-Bot-Fehler 409 Conflict beheben.

Wie viel Speicher er braucht

In unserem Test belegte der laufende Bot 48,5 MB Speicher (resident), nachdem er die 8 Befehle beantwortet hatte und mit offenem Pool, in einem Container mit 512 MB Limit wie bei unserem kleinsten Bot-Tarif. In unserem früheren Hosting-Test lag python-telegram-bot allein bei 44 MB (dieser Bot stoppte bei einem abgelehnten Token, bevor er eine Nachricht verarbeitet hatte), der Unterschied von etwa 4,5 MB umfasst also aiomysql, den Pool und die verarbeiteten Befehle. Die Installation von python-telegram-bot und aiomysql mit pip aus dem Nichts erreichte einen Spitzenwert von 73 MB, Dateicache eingerechnet. Die Bibliotheken im Vergleich: python-telegram-bot vs aiogram bei PlayNHost.

Dasselbe mit einer anderen Bibliothek

Du brauchst einen Server für deinen Telegram-Bot mit Datenbank inklusive? Sieh dir die Telegram-Bot-Tarife an.

Läuft dein Bot schon woanders? Zieh ihn zu uns um

UNSERE NEUESTEN BLOGS