NOWOŚĆ 📢 Ograniczona oferta na start: do 70% zniżki na pierwszy miesiąc, na każdą grę!Zobacz plany

Jak używać bazy danych MySQL z botem python-telegram-bot (aiomysql)

Hosting botów TelegramUpdated Nov 6, 202611 min read

W skrócie. Dodaj aiomysql do swojego bota python-telegram-bot, otwórz jedną pulę z autocommit=True w post_init, trzymaj ją w application.bot_data, żeby każdy handler miał do niej dostęp, i zamknij ją w post_shutdown. Używaj symboli zastępczych %s i przechowuj ID Telegrama jako BIGINT, a nie INT. W PlayNHost każdy plan bota zawiera jedną bazę MySQL: utwórz ją na stronie Bazy danych, wklej jej dane do panelu Środowisko, a kod poniżej zadziała bez zmian. Baza przyjmuje połączenia z twoich serwerów PlayNHost i z phpMyAdmin, nie z twojego komputera. Przetestowaliśmy ten kod 6 listopada 2026 z python-telegram-bot 22.8, aiomysql 0.3.2 i Pythonem 3.12, na MariaDB 10.11 z kontem bazy skonfigurowanym jak nasze (20 połączeń, limit zapytania 120 sekund) i zamiennikiem serwera Bot API Telegrama: 8 poleceń na wejściu, 8 odpowiedzi na wyjściu, notatki zapisane w bazie. Prawdziwe logowanie do Telegrama nie było częścią testu.

Czego potrzebujesz

1. Utwórz bazę danych

  1. Otwórz serwer swojego bota w panelu i kliknij Bazy danych.
  2. Kliknij Nowa baza danych. Etykieta jest opcjonalna; Zostanie utworzona jako pokazuje pełną nazwę. Kliknij Utwórz bazę danych.
  3. Otworzy się strona nowej bazy (później kliknij ją w Twoje bazy danych). Jej karta Połączenie pokazuje Adres hosta, Port, Baza danych, Nazwa użytkownika i Hasło. W sekcji Gotowa konfiguracja pole Konfiguracja dla zawiera fragment Python (aiomysql) z tymi samymi, już wpisanymi wartościami.

2. Wpisz dane w Środowisku

  1. Otwórz stronę Bot, panel Środowisko i kliknij Wklej plik .env.
  2. Wklej te pięć linii z wartościami z karty Połączenie:
    DB_HOST=your-host
    DB_PORT=3306
    DB_NAME=your-database
    DB_USER=your-username
    DB_PASSWORD=your-password
  3. DB_PASSWORD dostaje plakietkę Sekret: jest zapisywany tylko do zapisu i nigdy więcej nie jest pokazywany. Kliknij Zaimportuj 5 zmiennych.

Użyj prawdziwego portu z karty Połączenie. Token bota nie jest jedną z tych linii: token zapisany na stronie Bot, w panelu Telegram, trafia już do twojego kodu jako TELEGRAM_BOT_TOKEN, czyli nazwa, którą czyta kod poniżej (oraz jako BOT_TOKEN, TOKEN, TELEGRAM_TOKEN i TELOXIDE_TOKEN). Jeśli twój plik .env ma linię z tokenem, okno ją pomija. Więcej o tym panelu: Jak ustawić zmienne środowiskowe i sekrety dla bota Discord.

3. Kod

Mały bot z notatkami: /note buy milk zapisuje notatkę, /notes pokazuje twoje ostatnie pięć.

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

Co robi: run_polling() najpierw pyta Telegram, kim jest bot, potem uruchamia post_init i dopiero wtedy zaczyna pobierać wiadomości. post_init otwiera pulę, tworzy tabelę notes, jeśli jej brakuje, umieszcza pulę w application.bot_data["pool"] i wypisuje Database ready oraz Logged in as @your_bot. Każdy handler dostaje ten sam słownik jako context.bot_data. Gdy bot się zatrzymuje, run_polling() uruchamia post_shutdown, która zamyka pulę.

context.args to tekst po poleceniu, podzielony na słowa, więc kod składa go z powrotem pojedynczymi spacjami: podziały wierszy i podwójne spacje wewnątrz notatki zamieniają się w jedną spację. Jeśli potrzebujesz notatki dokładnie tak, jak została wpisana, weź update.effective_message.text i odetnij polecenie samodzielnie.

Symbole zastępcze %s wypełnia sterownik, więc notatka taka jak '); DROP TABLE notes; -- zostaje zapisana jako zwykły tekst. W naszym teście zapisała się jako wiersz 2, a tabela została. Nigdy nie buduj SQL za pomocą f-stringa.

Przechowuj ID Telegrama jako BIGINT

Dokumentacja Bot API Telegrama mówi, że ID użytkownika lub czatu „może mieć więcej niż 32 znaczące bity”, ale ma ich najwyżej 52, więc 64-bitowa liczba całkowita jest bezpieczna. Strona o ID podaje zakresy: ID użytkowników sięgają 1.099.511.627.775, a ID grup, supergrup i kanałów są ujemne. Supergrupy i kanały mieszczą się od -1.000.000.000.001 do -1.997.852.516.352 (na przykład -1001234567890). INT w MySQL kończy się na 2.147.483.647. W naszym teście, przy tym samym ścisłym trybie SQL, którego używa nasz serwer bazy, zapis ID użytkownika 7.000.000.001 w kolumnie INT się nie udał. aiomysql zgłosił:

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

Kolumna BIGINT go zapisała, -1001234567890 również. Użyj zwykłego BIGINT, a nie BIGINT UNSIGNED, żeby mieściły się też ID czatów.

4. Uruchom

  1. Wgraj bot.py i requirements.txt w Files.
  2. Naciśnij Uruchom i otwórz Console. Start instaluje pakiety z requirements.txt, a potem pokazuje [PlayNHost] Starting your bot, Database ready i Logged in as @your_bot. Ta ostatnia linia zmienia status w Przegląd na Online w Telegramie.
  3. W Telegramie wyślij swojemu botowi /note buy milk, a potem /notes. Żeby zobaczyć wiersze, otwórz stronę bazy i kliknij Otwórz w phpMyAdmin.

Od drugiego startu konsola pokazuje też Warning: Table 'notes' already exists, a przed tym ścieżkę do cursors.py z aiomysql. To uwaga MariaDB dla CREATE TABLE IF NOT EXISTS, przekazana przez sterownik. Nic złego się nie dzieje.

W naszym teście zamiennik Telegrama wysłał te polecenia i dostał te odpowiedzi:

WysłanoOdpowiedź
/note buy milkSaved. Send /notes to see your last five.
/noteWrite your note after the command, for example: /note buy milk
/note i 501 znakówA note can be up to 500 characters.
/notesZapisane notatki, od najnowszej
/notes od drugiego użytkownikaYou have no notes yet.

Jedna aktualizacja naraz

Przy ustawieniach domyślnych python-telegram-bot obsługuje jedną aktualizację naraz: następne polecenie czeka, aż poprzedni handler skończy. W naszym teście 4 polecenia, które przyszły razem, dostały odpowiedź w kolejności nadejścia, a po teście bot trzymał 1 otwarte połączenie. maxsize=5 nadal ma znaczenie, jeśli włączysz concurrent_updates w Application.builder() albo jeśli zadania i handlery odpytują bazę jednocześnie: pula nigdy nie otwiera więcej niż 5 połączeń, a dodatkowe zapytanie czeka na wolne.

Pułapka autocommit

aiomysql otwiera połączenia z wyłączonym autocommit. INSERT zostaje wtedy w otwartej transakcji. Gdy połączenie wraca do puli z wciąż otwartą transakcją, aiomysql je zamyka, a MariaDB odrzuca niezatwierdzony wiersz. W naszym teście wiersz wstawiony na puli bez autocommit=True był widoczny dla 0 innych połączeń. Przekaż autocommit=True do create_pool, jak wyżej, albo wywołaj await conn.commit() po zapisach.

Nieudane zapytanie nie zatrzymuje bota

W naszym teście usunęliśmy tabelę, gdy bot działał, i wysłaliśmy /notes. python-telegram-bot wypisał No error handlers are registered, logging exception. oraz traceback kończący się na pymysql.err.ProgrammingError: (1146, "Table '...notes' doesn't exist"), nie wysłał odpowiedzi na to polecenie i działał dalej: gdy tabela wróciła, następne /note zostało zapisane. Osoba, która wysłała polecenie, nic nie widzi, więc przechwyć błąd w handlerze albo zarejestruj handler błędów przez application.add_error_handler() i odpowiedz krótką wiadomością.

Spokojna noc nie psuje puli

Nasz serwer bazy zamyka połączenie, które było bezczynne przez 8 godzin. Bot, który tak długo nie dostaje wiadomości, na przykład w nocy, zastaje swoje połączenia w puli zamknięte. aiomysql sprawdza to, gdy handler prosi o połączenie, i otwiera nowe w miejsce zamkniętego. Przetestowaliśmy to z limitem bezczynności obniżonym do 5 sekund: po 7 spokojnych sekundach następne /note zostało zapisane bez błędu.

Limity ważne dla twojej puli

  • 20 połączeń naraz na użytkownika bazy. Liczy się każdy proces, który używa tego samego loginu. Połączenie 21 zostaje odrzucone z (1226, "User '...' has exceeded the 'max_user_connections' resource (current value: 20)"). maxsize=5 trzyma bota znacznie poniżej tego limitu.
  • Zapytanie trwające dłużej niż 120 sekund jest zatrzymywane. MariaDB kończy je błędem 1969, Query execution was interrupted (max_statement_time exceeded). W naszym wcześniejszym teście, na koncie skonfigurowanym tak samo, stało się to po 120,03 sekundy. Zapytania bota zwykle trwają milisekundy, więc dotyczy to tylko zapytania, które wymknęło się spod kontroli.
  • 500 MB na bazę. Strona Bazy danych pokazuje, ile jest zajęte. Przy 500 MB zapisy są zatrzymywane, dopóki nie usuniesz wierszy.
  • Kopie zapasowe serwera nie obejmują bazy danych. Co jakiś czas eksportuj ją z phpMyAdmin.

Błędy i co oznaczają

To błędy, które aiomysql zgłosił w naszym teście, zapisane tak, jak kończą traceback w konsoli (pochodzą z PyMySQL, którego aiomysql używa pod spodem):

  • pymysql.err.OperationalError: (1045, "Access denied for user ..."): zły DB_USER lub DB_PASSWORD. Po Zresetuj hasło na karcie Połączenie stare hasło przestaje działać od razu: zamień DB_PASSWORD i zrestartuj bota.
  • pymysql.err.OperationalError: (2003, "Can't connect to MySQL server on ..."): zły host lub port albo połączenie spoza PlayNHost. Baza przyjmuje tylko połączenia z serwerów PlayNHost i z phpMyAdmin, więc to samo dostaniesz na własnym komputerze.
  • pymysql.err.OperationalError: (1226, ... 'max_user_connections' ...): za dużo otwartych połączeń, zobacz limity wyżej. Na przykład druga kopia bota albo pula otwierana dla każdej wiadomości.
  • pymysql.err.DataError: (1264, "Out of range value for column 'user_id' at row 1"): kolumna z ID jest typu INT. Zmień ją na BIGINT.
  • pymysql.err.ProgrammingError: (1146, "Table '...' doesn't exist"): brakuje tabeli. Kod powyżej tworzy ją przy starcie; jeśli tabela powstała ręcznie, sprawdź jej nazwę.

Zmienna, której brakuje w Środowisko, zatrzymuje bota przed połączeniem, z KeyError: 'DB_HOST' lub nazwą brakującej zmiennej, bo kod czyta każdą wartość przez os.environ[...]. Dodaj ją i zrestartuj bota: zmienne obowiązują od następnego startu.

Zły token zatrzymuje bota przed częścią z bazą, bo post_init uruchamia się dopiero po tym, jak Telegram zaakceptuje token. Z naszym fałszywym tokenem wobec prawdziwego Telegrama bot nigdy nie wypisał Database ready i zatrzymał się po około 1 sekundzie z telegram.error.InvalidToken: The token `...` was rejected by the server. Ta wiadomość zawiera cały token, więc nie wklejaj nigdzie publicznie wyjścia konsoli, które go zawiera. Skopiuj token ponownie z @BotFather i wklej go na stronie Bot, w panelu Telegram. Więcej błędów Telegrama: Jak naprawić błąd 409 Conflict bota Telegram.

Ile pamięci zajmuje

W naszym teście działający bot zajmował 48,5 MB pamięci (rezydentnej) po odpowiedzi na 8 poleceń, z otwartą pulą, w kontenerze ograniczonym do 512 MB jak nasz najmniejszy plan bota. W naszym wcześniejszym teście hostingu sam python-telegram-bot zmierzył 44 MB (ten bot zatrzymał się na odrzuconym tokenie, zanim obsłużył jakąkolwiek wiadomość), więc różnica, około 4,5 MB, obejmuje aiomysql, pulę i obsłużone polecenia. Instalacja python-telegram-bot i aiomysql przez pip od zera osiągnęła szczyt 73 MB, razem z pamięcią podręczną plików. Biblioteki obok siebie: python-telegram-bot kontra aiogram w PlayNHost.

To samo z inną biblioteką

Potrzebujesz serwera dla swojego bota Telegram z wliczoną bazą danych? Zobacz plany dla botów Telegram.

Twój bot działa już gdzie indziej? Przenieś go do nas

NASZE OSTATNIE WPISY