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 python-telegram-bot (aiomysql)

Hospedagem de bot do TelegramUpdated Nov 6, 202613 min read

Resposta curta. Adicione aiomysql ao seu bot python-telegram-bot, abra um único pool com autocommit=True no post_init, guarde-o em application.bot_data para que todo handler consiga acessá-lo e feche-o no post_shutdown. Use marcadores %s e salve os IDs do Telegram como BIGINT, não como INT. Na PlayNHost, todo plano de bot inclui um banco de dados MySQL: crie-o na página Bancos de dados, cole os dados dele no painel 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 python-telegram-bot 22.8, aiomysql 0.3.2 e Python 3.12, contra MariaDB 10.11 com uma conta de banco configurada como a nossa (20 conexões, limite de consulta de 120 segundos) e um substituto do servidor da Bot API do Telegram: 8 comandos de entrada, 8 respostas de saída, 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 o servidor do seu bot no painel e clique em Bancos de dados.
  2. Clique em Novo banco de dados. Um 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 traz um trecho Python (aiomysql) com os mesmos valores já preenchidos.

2. Coloque os dados em Ambiente

  1. Abra a página Bot, o painel 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 recebe um selo Segredo: ele é salvo só para escrita e nunca mais é mostrado. 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, painel Telegram, já chega ao seu código como TELEGRAM_BOT_TOKEN, o nome que o código abaixo lê (e como BOT_TOKEN, TOKEN, TELEGRAM_TOKEN e TELOXIDE_TOKEN). Se o seu arquivo .env tiver uma linha de token, a janela a ignora. Mais sobre esse painel: 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

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

O que ele faz: run_polling() primeiro pergunta ao Telegram quem é o bot, depois executa post_init e só então começa a buscar mensagens. post_init abre o pool, cria a tabela notes se ela não existir, coloca o pool em application.bot_data["pool"] e imprime Database ready e Logged in as @your_bot. Todo handler recebe o mesmo dicionário como context.bot_data. Quando o bot para, run_polling() executa post_shutdown, que fecha o pool.

context.args é o texto depois do comando, dividido em palavras, então o código o junta de volta com espaços simples: quebras de linha e espaços duplos dentro de uma nota viram um só espaço. Se você precisa da nota exatamente como foi digitada, use update.effective_message.text e corte o comando você mesmo.

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.

Salve 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 tem no máximo 52, então um inteiro de 64 bits é seguro. A página sobre IDs traz as faixas: 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 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 o salvou, e -1001234567890 também. Use 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 e depois mostra [PlayNHost] Starting your bot, Database ready e Logged in as @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 e 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 cursors.py do aiomysql na frente. É o aviso do MariaDB para CREATE TABLE IF NOT EXISTS, repassado pelo driver. Não há nada de 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.

Uma atualização de cada vez

Com as configurações padrão, o python-telegram-bot trata uma atualização de cada vez: o próximo comando espera o handler anterior terminar. No nosso teste, 4 comandos que chegaram juntos foram respondidos na ordem em que chegaram, e depois do teste o bot mantinha 1 conexão aberta. maxsize=5 continua importando se você ativar concurrent_updates em Application.builder(), ou se jobs e handlers consultarem o banco ao mesmo tempo: o pool nunca abre mais de 5 conexões, e uma consulta extra espera por uma livre.

A armadilha do autocommit

O aiomysql abre 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 enquanto o bot rodava e enviamos /notes. O python-telegram-bot imprimiu No error handlers are registered, logging exception. e 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 capture o erro no handler, ou registre um handler com application.add_error_handler(), e responda com uma mensagem curta.

Uma noite tranquila não quebra o pool

O nosso servidor de banco fecha uma conexão que ficou ociosa por 8 horas. Um bot que não recebe 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: depois de 7 segundos de silêncio, o /note seguinte foi salvo sem 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 depois de 120,03 segundos. As consultas de um bot normalmente levam milissegundos, então isso só atinge uma consulta descontrolada.
  • 500 MB por banco de dados. 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 (vêm do PyMySQL, que o aiomysql usa por baixo):

  • pymysql.err.OperationalError: (1045, "Access denied for user ..."): DB_USER ou DB_PASSWORD errado. 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 da PlayNHost. O banco só aceita conexões de 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 demais abertas, veja os limites acima. Por exemplo, uma segunda cópia do bot ou um pool aberto a 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 na inicialização; se você a criou à mão, confira o nome.

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

Um token errado para o bot antes da parte do banco, porque post_init só roda depois que o Telegram aceita o token. Com o nosso token falso contra o Telegram de verdade, o bot nunca imprimiu Database ready e parou depois de cerca de 1 segundo com telegram.error.InvalidToken: The token `...` was rejected by the server. Essa mensagem contém o token inteiro, então não cole em nenhum lugar público uma saída do console que o contenha. Copie o token de novo do @BotFather e cole na página Bot, painel 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 48,5 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. No nosso teste de hospedagem anterior, o python-telegram-bot sozinho mediu 44 MB (esse bot parou em um token recusado, antes de tratar qualquer mensagem), então a diferença, cerca de 4,5 MB, cobre o aiomysql, o pool e os comandos tratados. Instalar python-telegram-bot e aiomysql com pip do zero chegou a um pico de 73 MB, incluindo o cache de arquivos. As bibliotecas lado a lado: 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 do Telegram.

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

NOSSOS ÚLTIMOS ARTIGOS