NOVO 📢 Oferta de lançamento limitada: até 70% de desconto no seu primeiro mês, em todos os jogos!Ver planos

Como usar um banco de dados MySQL com um bot aiogram (aiomysql)

Hospedagem de bot do TelegramUpdated Nov 6, 202612 min read

Resposta curta. Adicione aiomysql ao seu bot aiogram 3, abra um único pool com autocommit=True antes de o polling começar e entregue-o aos seus handlers com dp.start_polling(bot, pool=pool): o aiogram o repassa a todo handler que tenha um parâmetro pool. Use os marcadores %s e guarde os IDs do Telegram como BIGINT, não como INT. No PlayNHost, todo plano de bot inclui um banco de dados MySQL: crie-o na página Bancos de dados, cole os dados dele na seção Ambiente e o código abaixo funciona do jeito que está. O banco aceita conexões dos seus servidores PlayNHost e do phpMyAdmin, não do seu computador. Testamos este código em 6 de novembro de 2026 com aiogram 3.31.0, aiomysql 0.3.2 e Python 3.12, contra o MariaDB 10.11, com uma conta de banco configurada como a nossa (20 conexões, limite de 120 segundos por consulta) e um substituto do servidor Bot API do Telegram: 8 comandos entrando, 8 respostas saindo, as notas salvas no banco. Um login real no Telegram não fez parte do teste.

O que você precisa

1. Crie o banco de dados

  1. Abra seu servidor de bot no painel e clique em Bancos de dados.
  2. Clique em Novo banco de dados. O campo Rótulo é opcional; Será criado como mostra o nome completo. Clique em Criar banco de dados.
  3. A página do novo banco abre (depois, clique nele em Seus bancos de dados). O cartão Conexão mostra Host, Porta, Banco de dados, Nome de usuário e Senha. Em Config pronta para usar, Config para tem um snippet Python (aiomysql) com os mesmos valores já preenchidos.

2. Coloque os dados em Ambiente

  1. Abra a página Bot, seção Ambiente, e clique em Colar um arquivo .env.
  2. Cole estas cinco linhas com os valores do cartão Conexão:
    DB_HOST=your-host
    DB_PORT=3306
    DB_NAME=your-database
    DB_USER=your-username
    DB_PASSWORD=your-password
  3. DB_PASSWORD ganha um selo Segredo: ela é salva só para escrita e nunca mais é exibida. Clique em Importar 5 variáveis.

Use a porta real do cartão Conexão. O token do bot não é uma dessas linhas: o token que você salvou na página Bot, seção Telegram, já chega ao seu código como BOT_TOKEN, o nome que os próprios exemplos do aiogram usam (e como TELEGRAM_BOT_TOKEN, TOKEN, TELEGRAM_TOKEN e TELOXIDE_TOKEN). Se o seu arquivo .env tiver uma linha de token, a janela a ignora. Mais sobre esta seção: Como definir variáveis de ambiente e segredos para o seu bot do Discord.

3. O código

Um pequeno bot de notas: /note buy milk salva uma nota, /notes mostra as suas ú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())

O que ele faz: main() abre o pool, cria a tabela notes se ela não existir e imprime Database ready antes de o bot pedir mensagens ao Telegram. dp.start_polling(bot, pool=pool) é o jeito do próprio aiogram de compartilhar um objeto: toda palavra-chave extra que você passa vira um dado que o aiogram entrega a qualquer handler com um parâmetro desse nome, então note() e notes() recebem o mesmo pool. Quando o bot para, o bloco finally fecha o pool.

Os marcadores %s são preenchidos pelo driver, então uma nota como '); DROP TABLE notes; -- é salva como texto simples. No nosso teste, ela foi salva como a linha 2 e a tabela continuou lá. Nunca monte o SQL com uma f-string.

Guarde os IDs do Telegram como BIGINT

A documentação da Bot API do Telegram diz que um ID de usuário ou de chat “pode ter mais de 32 bits significativos”, mas no máximo 52, então um inteiro de 64 bits é seguro. A página sobre IDs dá os intervalos: IDs de usuário vão até 1.099.511.627.775, enquanto IDs de grupos, supergrupos e canais são negativos. Supergrupos e canais vão de -1.000.000.000.001 a -1.997.852.516.352 (por exemplo -1001234567890). O tipo INT do MySQL para em 2.147.483.647. No nosso teste, com o mesmo modo SQL estrito que o nosso servidor de banco usa, salvar o ID de usuário 7.000.000.001 em uma coluna INT falhou. O aiomysql lançou:

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

Uma coluna BIGINT armazenou o valor, e -1001234567890 também. Use um BIGINT simples, não BIGINT UNSIGNED, para que os IDs de chat também caibam.

4. Execute

  1. Envie bot.py e requirements.txt em Files.
  2. Pressione Iniciar e abra Console. A inicialização instala os pacotes de requirements.txt, depois mostra [PlayNHost] Starting your bot, Database ready e Run polling for bot @your_bot. Essa última linha muda o status em Visão geral para Online no Telegram.
  3. No Telegram, envie ao seu bot /note buy milk, depois /notes. Para ver as linhas, abra a página do banco e clique em Abrir no phpMyAdmin.

A partir da segunda inicialização, o console também mostra Warning: Table 'notes' already exists, com o caminho do arquivo cursors.py do aiomysql na frente. É o aviso do MariaDB para CREATE TABLE IF NOT EXISTS, repassado pelo driver. Nada está errado.

No nosso teste, o substituto do Telegram enviou estes comandos e recebeu estas respostas:

EnviadoResposta
/note buy milkSaved. Send /notes to see your last five.
/noteWrite your note after the command, for example: /note buy milk
/note e 501 caracteresA note can be up to 500 characters.
/notesAs notas salvas, da mais recente para a mais antiga
/notes de um segundo usuárioYou have no notes yet.

Muitas mensagens ao mesmo tempo

O aiogram trata cada atualização recebida como uma tarefa própria, então vários comandos podem executar suas consultas ao mesmo tempo. No nosso teste, 4 comandos que chegaram juntos foram tratados em cerca de 50 ms cada um. maxsize=5 limita o pool a 5 conexões; uma sexta consulta espera por uma livre em vez de abrir outra. Depois do teste, o bot mantinha 2 conexões abertas.

A armadilha do autocommit

O aiomysql abre as conexões com o autocommit desligado. Um INSERT fica então dentro de uma transação aberta. Quando a conexão volta ao pool com essa transação ainda aberta, o aiomysql a fecha, e o MariaDB descarta a linha não confirmada. No nosso teste, uma linha inserida em um pool sem autocommit=True foi vista por 0 outras conexões. Ou passe autocommit=True para create_pool, como acima, ou chame await conn.commit() depois das suas gravações.

Uma consulta com falha não derruba o bot

No nosso teste, apagamos a tabela com o bot rodando e enviamos /notes. O aiogram registrou o erro com um traceback terminando em pymysql.err.ProgrammingError: (1146, "Table '...notes' doesn't exist"), não enviou resposta a esse comando e continuou rodando: quando a tabela voltou, o /note seguinte foi salvo. Quem enviou o comando não vê nada, então, se uma consulta puder falhar, capture o erro no handler e responda com uma mensagem curta.

Uma noite tranquila não quebra o pool

Nosso servidor de banco fecha uma conexão ociosa por 8 horas. Um bot que fica sem mensagens por tanto tempo, de madrugada, por exemplo, encontra as conexões do pool fechadas. O aiomysql verifica isso quando um handler pede uma conexão e abre uma nova no lugar da fechada. Testamos com o limite de ociosidade reduzido para 5 segundos: após 7 segundos de silêncio, o /note seguinte foi salvo sem nenhum erro.

Limites que importam para o seu pool

  • 20 conexões ao mesmo tempo por usuário do banco. Todo processo que usa o mesmo login conta. A conexão 21 é recusada com (1226, "User '...' has exceeded the 'max_user_connections' resource (current value: 20)"). maxsize=5 mantém o bot bem abaixo disso.
  • Uma consulta que roda por mais de 120 segundos é interrompida. O MariaDB a encerra com o erro 1969, Query execution was interrupted (max_statement_time exceeded). Em um teste anterior nosso, em uma conta configurada do mesmo jeito, isso aconteceu após 120,03 segundos. As consultas de um bot normalmente levam milissegundos, então isso só atinge uma consulta fora de controle.
  • 500 MB por banco. A página Bancos de dados mostra quanto está em uso. Com 500 MB, as gravações são interrompidas até você apagar linhas.
  • Os backups do servidor não incluem o banco de dados. Exporte-o do phpMyAdmin de vez em quando.

Erros e o que significam

Estes são os erros que o aiomysql deu no nosso teste, escritos do jeito que terminam um traceback no console (eles vêm do PyMySQL, que o aiomysql usa por baixo):

  • pymysql.err.OperationalError: (1045, "Access denied for user ..."): valor errado de DB_USER ou DB_PASSWORD. Depois de Redefinir senha no cartão Conexão, a senha antiga para de funcionar na hora: troque DB_PASSWORD e reinicie o bot.
  • pymysql.err.OperationalError: (2003, "Can't connect to MySQL server on ..."): host ou porta errados, ou uma conexão de fora do PlayNHost. O banco só aceita conexões dos servidores PlayNHost e do phpMyAdmin, então é isso também que você recebe no seu computador.
  • pymysql.err.OperationalError: (1226, ... 'max_user_connections' ...): conexões abertas demais, veja os limites acima. Por exemplo, uma segunda cópia do bot ou um pool aberto para cada mensagem.
  • pymysql.err.DataError: (1264, "Out of range value for column 'user_id' at row 1"): uma coluna de ID é INT. Mude-a para BIGINT.
  • pymysql.err.ProgrammingError: (1146, "Table '...' doesn't exist"): a tabela não existe. O código acima a cria ao iniciar; se você a criou à mão, confira o nome dela.

Uma variável ausente em Ambiente interrompe o bot antes de ele conectar, com KeyError: 'DB_HOST' ou o nome da que faltar, porque o código lê cada valor com os.environ[...]. Adicione-a e reinicie o bot: as variáveis valem a partir da próxima inicialização.

Um token errado aparece depois da parte do banco. Com o nosso token falso contra o Telegram de verdade, o bot imprimiu Database ready, depois parou com aiogram.exceptions.TelegramUnauthorizedError: Telegram server says - Unauthorized. Copie o token de novo do @BotFather e cole-o na página Bot, seção Telegram. Mais erros do Telegram: Como corrigir o erro 409 Conflict do bot do Telegram.

Quanta memória ele usa

No nosso teste, o bot em execução usou 174 MB de memória (residente) depois de responder aos 8 comandos, com o pool aberto, em um contêiner limitado a 512 MB como o nosso menor plano de bot. Quase tudo isso é o próprio aiogram: no nosso teste de hospedagem anterior, só o aiogram mediu 173 MB (aquele bot parou em um token recusado, antes de tratar qualquer mensagem, então os dois números são próximos, mas não é uma comparação exata). Instalar aiogram e aiomysql com pip do zero chegou ao pico de 163 MB, incluindo o cache de arquivos. Isso deixa espaço para o seu código no menor plano. O guia de hospedagem compara as bibliotecas: python-telegram-bot vs aiogram na PlayNHost.

O mesmo com outra biblioteca

Precisa de um servidor para o seu bot do Telegram com banco de dados incluído? Veja os planos de bot para Telegram.

Seu bot já está em outro lugar? Traga-o para nós

NOSSOS ÚLTIMOS ARTIGOS