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 aiogram-Bot (aiomysql)

Telegram-Bot-HostingUpdated Nov 6, 202611 min read

Kurzantwort. Füge aiomysql zu deinem aiogram-3-Bot hinzu, öffne einen Pool mit autocommit=True bevor das Polling startet, und gib ihn mit dp.start_polling(bot, pool=pool): aiogram reicht ihn an jeden Handler weiter, der einen Parameter namens pool hat. Nutze %s als Platzhalter und speichere Telegram-IDs als BIGINT, nicht als INT. Bei PlayNHost ist in jedem Bot-Tarif eine MySQL-Datenbank enthalten: Lege sie auf der Seite Datenbanken an, füge ihre Daten im Bereich Umgebung ein, und der Code unten funktioniert so, wie er ist. Die Datenbank akzeptiert Verbindungen von deinen PlayNHost-Servern und phpMyAdmin, nicht von deinem eigenen Computer. Wir haben diesen Code am 6. November 2026 getestet, mit aiogram 3.31.0, 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 rein, 8 Antworten raus, die Notizen in der Datenbank gespeichert. Ein echter Telegram-Login 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; Wird erstellt als zeigt den vollständigen Namen. Klicke auf Datenbank erstellen.
  3. Die Seite der neuen Datenbank öffnet sich (später klickst du sie unter Deine Datenbanken). Die Karte Verbindung zeigt Host, Port, Datenbank, Benutzername und Passwort. Unter Fertige Config bietet Config für ein Snippet Python (aiomysql) mit denselben, schon eingetragenen Werten.

2. Die Daten unter Umgebung eintragen

  1. Öffne die Seite Bot, Bereich Umgebung, und klicke auf Eine .env-Datei einfügen.
  2. Füge diese fünf Zeilen mit den Werten aus 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 erhält das Secret-Badge: Er wird nur zum Schreiben gespeichert und nie wieder angezeigt. Klicke auf 5 Variablen importieren.

Nimm den echten Port aus der Karte Verbindung. Der Bot-Token gehört nicht zu diesen Zeilen: Der Token, den du auf der Seite Bot, Bereich Telegram, gespeichert hast, kommt in deinem Code schon als BOT_TOKEN an, der Name aus den eigenen Beispielen von aiogram (und als TELEGRAM_BOT_TOKEN, TOKEN, TELEGRAM_TOKEN und TELOXIDE_TOKEN). Hat deine .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 Notizen.

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

Was er tut: main() öffnet den Pool, legt die notes Tabelle an, falls sie fehlt, und gibt Database ready aus, bevor der Bot bei Telegram Nachrichten abholt. dp.start_polling(bot, pool=pool) ist die eigene Art von aiogram, ein Objekt zu teilen: Jedes zusätzliche Schlüsselwort, das du übergibst, wird zu Daten, die aiogram an jeden Handler mit einem Parameter dieses Namens weitergibt, sodass note() und notes() denselben Pool bekommen. Wenn der Bot stoppt, schließt der finally-Block den Pool.

Die %s-Platzhalter werden vom Treiber eingesetzt, deshalb wird eine Notiz wie '); DROP TABLE notes; -- als normaler Text gespeichert. In unserem Test landete sie als Zeile 2, und die Tabelle blieb bestehen. Baue das SQL nie mit einem f-String zusammen.

Telegram-IDs als BIGINT speichern

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

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

Eine BIGINT-Spalte speicherte sie, und -1001234567890 ebenfalls. Nimm ein einfaches BIGINT, nicht BIGINT UNSIGNED, damit auch Chat-IDs hineinpassen.

4. Ausführen

  1. Lade bot.py und requirements.txt unter Dateien hoch.
  2. Drücke Starten und öffne Konsole. Beim Start werden die Pakete aus requirements.txt installiert, danach siehst du [PlayNHost] Starting your bot, Database ready und Run polling for bot @your_bot. Die letzte Zeile setzt den Status unter Übersicht auf Online in Telegram.
  3. Sende deinem Bot in Telegram /note buy milk, dann /notes. Um 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, mit dem Pfad von aiomysqls cursors.py davor. Das ist der Hinweis von MariaDB zu CREATE TABLE IF NOT EXISTS, vom Treiber weitergereicht. Es ist nichts kaputt.

In unserem Test schickte der Telegram-Ersatz diese Befehle und bekam diese Antworten:

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, neueste zuerst
/notes von einem zweiten NutzerYou have no notes yet.

Viele Nachrichten gleichzeitig

aiogram verarbeitet jedes eingehende Update als eigene Task, daher können mehrere Befehle ihre Abfragen gleichzeitig ausführen. In unserem Test wurden 4 Befehle, die zusammen eintrafen, jeweils in etwa 50 ms verarbeitet. maxsize=5 begrenzt den Pool auf 5 Verbindungen; eine sechste Abfrage wartet auf eine freie, statt eine weitere zu öffnen. Nach dem Test hielt der Bot 2 offene Verbindungen.

Die Autocommit-Falle

aiomysql öffnet Verbindungen mit ausgeschaltetem Autocommit. Ein INSERT bleibt dann in einer offenen Transaktion. Geht die Verbindung mit noch offener Transaktion zurück in den Pool, schließt aiomysql sie, und MariaDB wirft die nicht bestätigte Zeile weg. In unserem Test wurde eine Zeile, die über einen Pool ohne autocommit=True eingefügt wurde, von 0 anderen Verbindungen gesehen. Übergib entweder autocommit=True an create_pool, wie oben, oder rufe await conn.commit() nach deinen Schreibvorgängen 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. aiogram protokollierte den Fehler mit einem Traceback, der auf pymysql.err.ProgrammingError: (1146, "Table '...notes' doesn't exist") endet, schickte keine Antwort auf diesen Befehl und lief weiter: Sobald die Tabelle wieder da war, wurde das nächste /note gespeichert. Wer den Befehl gesendet hat, sieht nichts; fange den Fehler also im Handler ab und antworte mit einer kurzen Nachricht, wenn eine Abfrage fehlschlagen kann.

Eine ruhige Nacht legt den Pool nicht lahm

Unser Datenbankserver schließt eine Verbindung, die 8 Stunden untätig war. Ein Bot, der so lange keine Nachrichten bekommt, etwa über Nacht, findet seine gepoolten Verbindungen geschlossen. aiomysql prüft das, wenn ein Handler eine Verbindung anfordert, und öffnet eine neue anstelle einer geschlossenen. 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 zählen

  • 20 Verbindungen gleichzeitig pro Datenbankbenutzer. Jeder Prozess, der dasselbe Login nutzt, 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 weit darunter.
  • Eine Abfrage, die länger als 120 Sekunden läuft, wird gestoppt. MariaDB beendet sie mit Fehler 1969, Query execution was interrupted (max_statement_time exceeded). In einem früheren Test von uns kam das auf einem gleich eingerichteten Konto nach 120,03 Sekunden. Die Abfragen eines Bots dauern normalerweise Millisekunden, deshalb trifft das nur eine außer Kontrolle geratene Abfrage.
  • 500 MB pro Datenbank. Auf der 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 ab und zu aus phpMyAdmin.

Fehler und was sie bedeuten

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

  • pymysql.err.OperationalError: (1045, "Access denied for user ..."): falscher DB_USER oder 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 akzeptiert nur Verbindungen von PlayNHost-Servern und phpMyAdmin, das bekommst du also auch auf deinem eigenen Computer.
  • 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 in Umgebung fehlende Variable stoppt den Bot, bevor er sich verbindet, mit KeyError: 'DB_HOST' oder dem Namen der fehlenden, weil der Code jeden Wert mit os.environ[...] liest. Füge sie hinzu und starte den Bot neu: Variablen gelten ab dem nächsten Start.

Ein falscher Token fällt erst nach dem Datenbankteil auf. Mit unserem falschen Token gegen das echte Telegram gab der Bot Database ready aus und stoppte dann mit aiogram.exceptions.TelegramUnauthorizedError: Telegram server says - Unauthorized. Kopiere den Token erneut von @BotFather und füge ihn auf der Seite Bot, Bereich Telegram. Mehr Telegram-Fehler: Telegram-Bot-Fehler 409 Conflict beheben.

Wie viel Speicher er braucht

In unserem Test belegte der laufende Bot 174 MB Speicher (resident), nachdem er die 8 Befehle beantwortet hatte, mit offenem Pool, in einem auf 512 MB begrenzten Container wie bei unserem kleinsten Bot-Tarif. Das meiste davon ist aiogram selbst: In unserem früheren Hosting-Test maß aiogram allein 173 MB (dieser Bot stoppte an einem abgelehnten Token, bevor er eine Nachricht bearbeitete, die beiden Zahlen liegen also nah beieinander, sind aber kein exakter Vergleich). Die Installation von aiogram und aiomysql mit pip von Grund auf erreichte 163 MB, Dateicache eingerechnet. Das lässt auf dem kleinsten Tarif Platz für deinen eigenen Code. Die Hosting-Anleitung vergleicht die Bibliotheken: python-telegram-bot vs aiogram bei PlayNHost.

Dasselbe mit einer anderen Bibliothek

Brauchst du einen Server für deinen Telegram-Bot mit enthaltener Datenbank? Zu den Telegram-Bot-Tarifen.

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

UNSERE NEUESTEN BLOGS