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

Hosting de bots de TelegramUpdated Nov 6, 202613 min read

Respuesta corta. Añade aiomysql a tu bot de aiogram 3, abre un único pool con autocommit=True antes de que empiece el polling y pásaselo a tus handlers con dp.start_polling(bot, pool=pool): aiogram lo entrega a cada handler que tenga un parámetro pool. Usa %s como marcadores de posición y guarda los IDs de Telegram como BIGINT, no como INT. En PlayNHost cada plan de bots incluye una base de datos MySQL: créala en la página Bases de datos, pega sus datos en la sección Entorno y el código de abajo funciona tal cual. La base de datos acepta conexiones desde tus servidores de PlayNHost y phpMyAdmin, no desde tu propio ordenador. Probamos este código el 6 de noviembre de 2026 con aiogram 3.31.0, 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 120 segundos por consulta) y un sustituto del servidor de la Bot API de Telegram: 8 comandos de entrada, 8 respuestas de salida, 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. La 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, sección 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 la insignia Secreto: se guarda solo para escritura y nunca 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, sección Telegram, ya llega a tu código como BOT_TOKEN, el nombre que usan los propios ejemplos de aiogram (y como TELEGRAM_BOT_TOKEN, TOKEN, TELEGRAM_TOKEN y TELOXIDE_TOKEN). Si tu archivo .env tiene una línea con el token, el cuadro de diálogo la omite. Más sobre esta sección: 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, /notes muestra tus últimas cinco.

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

Qué hace: main() abre el pool, crea la tabla notes si no existe e imprime Database ready antes de que el bot pida mensajes a Telegram. dp.start_polling(bot, pool=pool) es la forma propia de aiogram de compartir un objeto: cada argumento extra que pasas se convierte en datos que aiogram entrega a cualquier handler con un parámetro de ese nombre, así que note() y notes() reciben el mismo pool. Cuando el bot se detiene, el bloque finally cierra el pool.

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 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 como máximo 52, así que un entero de 64 bits es seguro. Su página sobre 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 tipo INT de MySQL llega solo 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 un BIGINT normal, no BIGINT UNSIGNED, para que quepan también los IDs de chat.

4. Ejecútalo

  1. Sube bot.py y requirements.txt en Archivos.
  2. Pulsa Iniciar y abre Consola. Al iniciar se instalan los paquetes de requirements.txt, y luego aparecen [PlayNHost] Starting your bot, Database ready y Run polling for bot @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 luego /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 archivo 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 recibió 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, la más reciente primero
/notes de un segundo usuarioYou have no notes yet.

Muchos mensajes a la vez

aiogram trata cada actualización entrante como su propia tarea, así que varios comandos pueden ejecutar sus consultas a la vez. En nuestra prueba, 4 comandos que llegaron juntos se atendieron cada uno en unos 50 ms. maxsize=5 limita el pool a 5 conexiones; una sexta consulta espera a que haya una libre en lugar de abrir otra. Tras la prueba, el bot mantenía 2 conexiones abiertas.

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 aún 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 conexiones más. O bien pasa autocommit=True a create_pool, como arriba, o llama a await conn.commit() después de tus escrituras.

Una consulta fallida no detiene el bot

En nuestra prueba borramos la tabla mientras el bot funcionaba y enviamos /notes. aiogram registró el error con un traceback que acababa en pymysql.err.ProgrammingError: (1146, "Table '...notes' doesn't exist"), no envió ninguna respuesta a ese comando y siguió funcionando: cuando la tabla volvió, el siguiente /note se guardó. Quien envía el comando no ve nada, así que, si una consulta puede fallar, captura el error en el 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 por la noche, se encuentra con sus conexiones del pool cerradas. 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 bajado a 5 segundos: tras 7 segundos de silencio, el siguiente /note se guardó sin ningún error.

Límites que importan para tu pool

  • 20 conexiones a la vez por usuario de base de datos. Cada proceso que use el mismo login cuenta. La conexión 21 se rechaza con (1226, "User '...' has exceeded the 'max_user_connections' resource (current value: 20)"). maxsize=5 mantiene al bot muy por debajo.
  • Una consulta que tarda 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, eso ocurrió a los 120,03 segundos. Las consultas de un bot suelen tardar milisegundos, así que solo afecta a una consulta descontrolada.
  • 500 MB por base de datos. La página Bases de datos muestra cuánto se usa. Al llegar a 500 MB, las escrituras se detienen 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 tal como terminan un traceback en la consola (vienen de PyMySQL, que aiomysql usa por debajo):

  • pymysql.err.OperationalError: (1045, "Access denied for user ..."): valor incorrecto de DB_USER o DB_PASSWORD. 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 para 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 arrancar; si la hiciste a mano, revisa su nombre.

Una variable que falta en Entorno detiene el bot antes de que se conecte, 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 en el siguiente inicio.

Un token incorrecto aparece después de la parte de la base de datos. Con nuestro token falso contra el Telegram real, el bot imprimió Database ready, y luego se detuvo con aiogram.exceptions.TelegramUnauthorizedError: Telegram server says - Unauthorized. Copia de nuevo el token desde @BotFather y pégalo en la página Bot, sección 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ó 174 MB de memoria (residente) después de responder a los 8 comandos, con su pool abierto, en un contenedor limitado a 512 MB como nuestro plan de bots más pequeño. Casi todo es aiogram en sí: en nuestra prueba de hosting anterior, aiogram solo midió 173 MB (ese bot se detuvo por un token rechazado, antes de procesar ningún mensaje, así que las dos cifras son parecidas pero no una comparación exacta). Instalar aiogram y aiomysql con pip desde cero llegó a un pico de 163 MB, caché de archivos incluida. Eso deja margen para tu propio código en el plan más pequeño. La guía de hosting compara las librerías: python-telegram-bot vs aiogram en PlayNHost.

Lo mismo con otra librería

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

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

NUESTROS ÚLTIMOS BLOGS