NUEVO 📢 Oferta de lanzamiento limitada: ¡hasta un 70% de descuento en tu primer mes, en todos los juegos!Ver planes

Cómo usar una base de datos MySQL con un bot de python-telegram-bot (aiomysql)

Hosting de bots de TelegramUpdated Nov 6, 202613 min read

Respuesta corta. Añade aiomysql a tu bot de python-telegram-bot, abre un solo pool con autocommit=True en post_init, guárdalo en application.bot_data para que todos los handlers lleguen a él y ciérralo en post_shutdown. Usa marcadores %s y guarda los IDs de Telegram como BIGINT, no como INT. En PlayNHost cada plan de bot incluye una base de datos MySQL: créala en la página Bases de datos, pega sus datos en el panel Entorno y el código de abajo funciona tal cual. La base de datos acepta conexiones desde tus servidores de PlayNHost y desde phpMyAdmin, no desde tu propio ordenador. Probamos este código el 6 de noviembre de 2026 con python-telegram-bot 22.8, aiomysql 0.3.2 y Python 3.12, contra MariaDB 10.11 con una cuenta de base de datos configurada como la nuestra (20 conexiones, límite de consulta de 120 segundos) y un sustituto del servidor de la Bot API de Telegram: 8 comandos de entrada, 8 respuestas de salida y las notas guardadas en la base de datos. Un inicio de sesión real en Telegram no formó parte de la prueba.

Qué necesitas

1. Crea la base de datos

  1. Abre tu servidor de bot en el panel y haz clic en Bases de datos.
  2. Haz clic en Base de datos nueva. Una Etiqueta es opcional; Se creará como muestra el nombre completo. Haz clic en Crear base de datos.
  3. Se abre la página de la nueva base de datos (más adelante, haz clic en ella en Tus bases de datos). Su tarjeta Conexión muestra Host, Puerto, Base de datos, Nombre de usuario y Contraseña. En Config lista para usar, Config para tiene un fragmento Python (aiomysql) con los mismos valores ya rellenados.

2. Pon los datos en Entorno

  1. Abre la página Bot, el panel Entorno, y haz clic en Pegar un archivo .env.
  2. Pega estas cinco líneas con los valores de la tarjeta Conexión:
    DB_HOST=your-host
    DB_PORT=3306
    DB_NAME=your-database
    DB_USER=your-username
    DB_PASSWORD=your-password
  3. DB_PASSWORD recibe una insignia Secreto: se guarda solo para escritura y no se vuelve a mostrar. Haz clic en Importar 5 variables.

Usa el puerto real de la tarjeta Conexión. El token del bot no es una de estas líneas: el token que guardaste en la página Bot, panel Telegram, ya llega a tu código como TELEGRAM_BOT_TOKEN, el nombre que lee el código de abajo (y como BOT_TOKEN, TOKEN, TELEGRAM_TOKEN y TELOXIDE_TOKEN). Si tu archivo .env tiene una línea de token, el diálogo la omite. Más sobre este panel: Cómo definir variables de entorno y secretos para tu bot de Discord.

3. El código

Un pequeño bot de notas: /note buy milk guarda una nota y /notes muestra tus últimas cinco.

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

Qué hace: run_polling() primero pregunta a Telegram quién es el bot, luego ejecuta post_init y solo después empieza a recibir mensajes. post_init abre el pool, crea la tabla notes si no existe, guarda el pool en application.bot_data["pool"] e imprime Database ready y Logged in as @your_bot. Todos los handlers reciben el mismo diccionario como context.bot_data. Cuando el bot se detiene, run_polling() ejecuta post_shutdown, que cierra el pool.

context.args es el texto que sigue al comando, dividido en palabras, así que el código lo vuelve a unir con espacios simples: los saltos de línea y los espacios dobles dentro de una nota se convierten en un solo espacio. Si necesitas la nota exactamente como se escribió, toma update.effective_message.text y recorta el comando tú mismo.

Los marcadores %s los rellena el driver, así que una nota como '); DROP TABLE notes; -- se guarda como texto normal. En nuestra prueba se guardó como la fila 2 y la tabla siguió ahí. No construyas nunca el SQL con un f-string.

Guarda los IDs de Telegram como BIGINT

La documentación de la Bot API de Telegram dice que un ID de usuario o de chat «puede tener más de 32 bits significativos» pero tiene como máximo 52, así que un entero de 64 bits es seguro. Su página sobre los IDs da los rangos: los IDs de usuario llegan hasta 1.099.511.627.775, mientras que los IDs de grupos, supergrupos y canales son negativos. Los supergrupos y canales van de -1.000.000.000.001 a -1.997.852.516.352 (por ejemplo -1001234567890). El INT de MySQL llega hasta 2.147.483.647. En nuestra prueba, con el mismo modo SQL estricto que usa nuestro servidor de base de datos, guardar el ID de usuario 7.000.000.001 en una columna INT falló. aiomysql lanzó:

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

Una columna BIGINT lo guardó, y -1001234567890 también. Usa BIGINT a secas, no BIGINT UNSIGNED, para que también quepan los IDs de chat.

4. Ejecútalo

  1. Sube bot.py y requirements.txt en Archivos.
  2. Pulsa Iniciar y abre Consola. El inicio instala los paquetes de requirements.txt y luego muestra [PlayNHost] Starting your bot, Database ready y Logged in as @your_bot. Esa última línea cambia el estado en Resumen a En línea en Telegram.
  3. En Telegram, envía a tu bot /note buy milk y después /notes. Para ver las filas, abre la página de la base de datos y haz clic en Abrir en phpMyAdmin.

A partir del segundo inicio, la consola también muestra Warning: Table 'notes' already exists, con la ruta del cursors.py de aiomysql delante. Es el aviso de MariaDB para CREATE TABLE IF NOT EXISTS, transmitido por el driver. No pasa nada.

En nuestra prueba el sustituto de Telegram envió estos comandos y obtuvo estas respuestas:

EnviadoRespuesta
/note buy milkSaved. Send /notes to see your last five.
/noteWrite your note after the command, for example: /note buy milk
/note y 501 caracteresA note can be up to 500 characters.
/notesLas notas guardadas, las más recientes primero
/notes de un segundo usuarioYou have no notes yet.

Una actualización cada vez

Con su configuración por defecto, python-telegram-bot procesa una actualización cada vez: el siguiente comando espera a que el handler anterior termine. En nuestra prueba, 4 comandos que llegaron juntos se respondieron en el orden en que llegaron, y después de la prueba el bot tenía 1 conexión abierta. maxsize=5 sigue importando si activas concurrent_updates en Application.builder(), o si los jobs y los handlers consultan a la vez: el pool nunca abre más de 5 conexiones y una consulta extra espera a que haya una libre.

La trampa del autocommit

aiomysql abre las conexiones con autocommit desactivado. Un INSERT se queda entonces dentro de una transacción abierta. Cuando la conexión vuelve al pool con esa transacción todavía abierta, aiomysql la cierra y MariaDB descarta la fila sin confirmar. En nuestra prueba, una fila insertada en un pool sin autocommit=True fue vista por 0 otras conexiones. O pasas autocommit=True a create_pool, como arriba, o llamas a await conn.commit() después de tus escrituras.

Una consulta fallida no detiene el bot

En nuestra prueba borramos la tabla con el bot en marcha y enviamos /notes. python-telegram-bot imprimió No error handlers are registered, logging exception. y un traceback que terminaba en pymysql.err.ProgrammingError: (1146, "Table '...notes' doesn't exist"), no envió respuesta a ese comando y siguió funcionando: cuando la tabla volvió, el siguiente /note se guardó. Quien envió el comando no ve nada, así que captura el error en el handler, o registra un handler con application.add_error_handler(), y responde con un mensaje corto.

Una noche tranquila no rompe el pool

Nuestro servidor de base de datos cierra una conexión que lleva 8 horas inactiva. Un bot que no recibe mensajes durante tanto tiempo, por ejemplo de madrugada, encuentra cerradas las conexiones de su pool. aiomysql lo comprueba cuando un handler pide una conexión y abre una nueva en lugar de la cerrada. Lo probamos con el límite de inactividad reducido a 5 segundos: tras 7 segundos de calma, el siguiente /note se guardó sin errores.

Límites que importan para tu pool

  • 20 conexiones a la vez por usuario de base de datos. Cuenta cada proceso que use el mismo login. La conexión 21 se rechaza con (1226, "User '...' has exceeded the 'max_user_connections' resource (current value: 20)"). maxsize=5 mantiene el bot muy por debajo.
  • Una consulta que dura más de 120 segundos se detiene. MariaDB la termina con el error 1969, Query execution was interrupted (max_statement_time exceeded). En una prueba anterior nuestra, en una cuenta configurada igual, ocurrió a los 120,03 segundos. Las consultas de un bot suelen tardar milisegundos, así que esto solo afecta a una consulta descontrolada.
  • 500 MB por base de datos. La página Bases de datos muestra cuánto se usa. A los 500 MB se detienen las escrituras hasta que borres filas.
  • Las copias de seguridad del servidor no incluyen la base de datos. Expórtala desde phpMyAdmin de vez en cuando.

Errores y qué significan

Estos son los errores que dio aiomysql en nuestra prueba, escritos como terminan un traceback en la consola (vienen de PyMySQL, que aiomysql usa por debajo):

  • pymysql.err.OperationalError: (1045, "Access denied for user ..."): DB_USER o DB_PASSWORD incorrectos. Tras Restablecer contraseña en la tarjeta Conexión, la contraseña antigua deja de funcionar al instante: sustituye DB_PASSWORD y reinicia el bot.
  • pymysql.err.OperationalError: (2003, "Can't connect to MySQL server on ..."): host o puerto incorrectos, o una conexión desde fuera de PlayNHost. La base de datos solo acepta conexiones desde servidores de PlayNHost y phpMyAdmin, así que esto es también lo que obtienes desde tu propio ordenador.
  • pymysql.err.OperationalError: (1226, ... 'max_user_connections' ...): demasiadas conexiones abiertas, mira los límites de arriba. Por ejemplo, una segunda copia del bot o un pool abierto por cada mensaje.
  • pymysql.err.DataError: (1264, "Out of range value for column 'user_id' at row 1"): una columna de ID es INT. Cámbiala a BIGINT.
  • pymysql.err.ProgrammingError: (1146, "Table '...' doesn't exist"): falta la tabla. El código de arriba la crea al iniciar; si la hiciste a mano, revisa su nombre.

Una variable que falta en Entorno detiene el bot antes de conectar, con KeyError: 'DB_HOST' o el nombre de la que falte, porque el código lee cada valor con os.environ[...]. Añádela y reinicia el bot: las variables se aplican la próxima vez que arranca.

Un token incorrecto detiene el bot antes de la parte de la base de datos, porque post_init solo se ejecuta después de que Telegram haya aceptado el token. Con nuestro token falso contra el Telegram real, el bot nunca imprimió Database ready y se detuvo tras aproximadamente 1 segundo con telegram.error.InvalidToken: The token `...` was rejected by the server. Ese mensaje contiene el token entero, así que no pegues en ningún sitio público la salida de la consola que lo incluya. Copia de nuevo el token desde @BotFather y pégalo en la página Bot, panel Telegram. Más errores de Telegram: Solucionar el error 409 Conflict de un bot de Telegram.

Cuánta memoria usa

En nuestra prueba el bot en marcha usó 48,5 MB de memoria (residente) tras responder los 8 comandos, con su pool abierto, en un contenedor limitado a 512 MB como nuestro plan de bot más pequeño. En nuestra prueba de alojamiento anterior, python-telegram-bot solo midió 44 MB (ese bot se detuvo ante un token rechazado, antes de procesar ningún mensaje), así que la diferencia, unos 4,5 MB, cubre aiomysql, el pool y los comandos procesados. Instalar python-telegram-bot y aiomysql con pip desde cero alcanzó un pico de 73 MB, caché de archivos incluida. Las bibliotecas lado a lado: python-telegram-bot vs aiogram en PlayNHost.

Lo mismo con otra biblioteca

¿Necesitas un servidor para tu bot de Telegram con base de datos incluida? Mira los planes de bots de Telegram.

¿Ya tienes tu bot en otro proveedor? Tráelo con nosotros

NUESTROS ÚLTIMOS BLOGS