Tratamento de Erros de Inserção de Inteiros no MySQL 8.0 com Python

Ao migrar ambientes de desenvolvimento ou produção de versões legadas do MySQL (como a 5.1) para versões mais recentes (como a 8.0), desenvolvedores frequentemente encontram comportamentos distintos na validação de tipos de dados. Um problema comum ocorre ao tentar inserir strings vazias em colunas definidas como inteiras.

Mudanças no Comportamento do MySQL

Existem dois pontos fundamentais que alteraram a percepção e o funcionamento do banco de dados em versões recentes:

  1. Largura de Exibição (Display Width): A partir da versão 8.0.17, o MySQL descontinuou o suporte para a largura de exibição em tipos inteiros (como INT(10)). Isso significa que, ao salvar a estrutura de uma tabela, o Navicat ou outras ferramentas podem exibir o comprimento como 0 ou omiti-lo, pois essa configuração não afeta mais o armazenamento ou o intervalo de valores.
  2. Modo Estrito: Versões modernas operam por padrão com modos SQL mais rigorosos. Enquanto o MySQL 5.1 convertia silenciosamente uma string vazia ('') para 0 ao inserir em um campo INT, o MySQL 8.0 gera o erro 1366: Incorrect integer value.

Demonstração do Problema

O código abaixo exemplifica uma tentativa de inserção que funciona no MySQL 5.1, mas falha no MySQL 8.0:

import pymysql

# Configuração de conexão
config = {
    'host': 'localhost',
    'user': 'root',
    'password': 'sua_senha',
    'database': 'ecommerce'
}

username = 'DevSenior'
score = ''  # Representando um dado ausente vindo de um formulário ou crawler

conn = pymysql.connect(**config)
try:
    with conn.cursor() as cursor:
        sql = "INSERT INTO perfil_usuario (nome, pontuacao) VALUES (%s, %s)"
        cursor.execute(sql, (username, score))
        conn.commit()
except pymysql.MySQLError as err:
    print(f"Erro na transação: {err}")
    conn.rollback()
finally:
    conn.close()

No MySQL 8.0, o resultado será: (1366, "Incorrect integer value: '' for column 'pontuacao' at row 1").

Solução Técnica: Higienização de Dados

A abordagem mais robusta para resolver este impasse é garantir que a lógica da aplicação trate os dados antes de enviá-los para a camada de persistência. Em vez de depender de conversões implícitas do motor de banco de dados, devemos converter explicitamente strings vazias em 0 ou None (que será interpretado como NULL).

Abaixo, apresento uma implemantação de um método de inserção genérico que realiza a limpeza automática de campos específicos:

def salvar_registro(self, nome_tabela, dicionario_dados):
    """
    Insere dados em uma tabela, tratando strings vazias para evitar erros de tipo.
    """
    # Lista de campos que devem ser obrigatoriamente numéricos
    campos_numericos = ['vendas', 'comentarios', 'pontuacao']

    # Sanitização: converte strings vazias em 0 para campos numéricos
    for campo in campos_numericos:
        if dicionario_dados.get(campo) == '':
            dicionario_dados[campo] = 0

    colunas = ', '.join(dicionario_dados.keys())
    marcadores = ', '.join(['%s'] * len(dicionario_dados))
    
    query_sql = f"INSERT INTO {nome_tabela} ({colunas}) VALUES ({marcadores})"

    try:
        self.cursor.execute(query_sql, tuple(dicionario_dados.values()))
        self.db.commit()
    except pymysql.MySQLError as erro:
        print(f"Falha ao persistir dados: {erro.args}")
        self.db.rollback()

Considerações sobre NULL vs 0

Dependendo da regra de negócio, pode ser mais apropriado converter a string vazia para None em vez de 0. No Python, ao passar None para o pymysql, o driver enviará o comando NULL para o MySQL. Isso é útil quando você precisa distinguir entre um valer zerado e um valor não informado, desde que a coluna no banco de dados permita valores nulos.

# Exemplo de conversão para NULL
if data.get('comments') == '':
    data['comments'] = None

Ao adotar essa prática de sanitização, o código torna-se independente da versão do servidor MySQL, evitando quebras inesperadas durante atualizações de infraestrutura.

Tags: MySQL Python PyMySQL database-migration backend

Publicado em 10-15 20:36