Como Resolver Timeout no PHP e MySQL ao Salvar Grandes Volumes de Dados

Aprenda a eliminar timeouts no PHP e MySQL ao salvar listas extensas de dados. Técnicas de bulk insert, transações PDO e otimização de consultas.

Como Resolver Timeout no PHP e MySQL ao Salvar Grandes Volumes de Dados

Uma requisição que expira após 30 ou 60 segundos de processamento é um dos sintomas mais frustrantes em sistemas corporativos. O cenário é recorrente: uma rotina precisa persistir algumas centenas de registros — como 300 linhas de peças de reposição ou itens de faturamento — e o servidor simplesmente atinge o limite de execução (max_execution_time ou timeout de gateway 504).

Aumentar cegamente os limites de tempo do servidor não resolve a causa raiz; apenas adia o colapso do sistema à medida que a base cresce. A solução real reside na identificação de gargalos de I/O, reestruturação das operações de escrita e ajuste da camada de banco de dados.


As Causas Ocultas do Timeout em Processamentos em Lote

Quando um script PHP demora dezenas de segundos para salvar poucas centenas de linhas, o problema raramente está no hardware. Na maioria das vezes, decorre de decisões arquiteturais na interação entre aplicação e banco de dados:

  1. O antipattern da escrita em loop (N+1 Writes): Executar um INSERT ou UPDATE individual dentro de um loop foreach de 300 iterações significa abrir e fechar 300 transações de rede e de disco. O custo de round-trip de rede e confirmação em disco (fsync) do MySQL degrada a performance de forma exponencial.
  2. Ausência de transações explícitas: No motor InnoDB, cada instrução isolada roda em modo autocommit. Isso força o banco a validar e gravar cada linha individualmente no log de transações.
  3. Gatilhos (Triggers) e Índices excessivos: Cada inserção obriga a reconstrução de árvores de índices secundários e, se houver triggers atreladas à tabela, o processamento por linha se multiplica.
  4. Consultas de validação redundantes: Checar se cada item já existe na base antes de inserir, fazendo um SELECT por iteração, transforma 300 operações em 600 ou 900 chamadas ao MySQL.

Estratégias Técnicas de Otimização

1. Inserção em Lote (Bulk Insert)

Em vez de 300 queries separadas, consolide os dados em uma única instrução estruturada:

sql
INSERT INTO pecas_reposicao (codigo, descricao, quantidade, valor)
VALUES
(‘A1’, ‘Correia Dentada’, 10, 45.50),
(‘B2’, ‘Filtro de Óleo’, 5, 28.00),
(‘C3’, ‘Pastilha de Freio’, 2, 89.90);

Essa abordagem reduz drasticamente a sobrecarga de rede e permite que o MySQL serialize as gravações em um único bloco de I/O.

2. Uso de Transações Manuais

Se as operações exigirem comandos separados ou lógicas condicionais complexas, envolva o bloco em uma transação explícita com PDO:

php
try {
$pdo->beginTransaction();

$stmt = $pdo->prepare("INSERT INTO pecas_reposicao (codigo, quantidade) VALUES (?, ?)");
foreach ($itens as $item) {
    $stmt->execute([$item['codigo'], $item['quantidade']]);
}

$pdo->commit();

} catch (Exception $e) {
$pdo->rollBack();
throw $e;
}

Apenas agrupando as operações em um único commit, o tempo de execução costuma cair de dezenas de segundos para frações de segundo.

3. Sincronização Inteligente com UPSERT

Quando a operação envolve atualizar registros existentes e inserir novos, o comando ON DUPLICATE KEY UPDATE elimina a necessidade de executar SELECT prévios para cada linha:

sql
INSERT INTO pecasreposicao (codigo, estoque, atualizadoem)
VALUES (?, ?, NOW())
ON DUPLICATE KEY UPDATE
estoque = VALUES(estoque),
atualizado_em = NOW();


Fluxo de Diagnóstico e Resolução

Em sistemas que desenvolvo, a resolução de lentidão em persistência segue um processo metódico para garantir eficiência sem introduzir inconsistências:

  1. Perfilamento com MySQL Slow Query Log: Ativação temporária do log de queries lentas para isolar exatamente quais instruções demoram mais de 1 segundo.
  2. Análise de Plano de Execução (EXPLAIN): Verificação de full table scans ou bloqueios em cascata gerados por chaves estrangeiras sem índices adequados.
  3. Refatoração da Camada de Aplicação: Divisão dos dados em blocos menores (chunks de 100 a 500 registros) no PHP para evitar consumo excessivo de memória (memory_limit).
  4. Testes de Estresse Controlados: Simulação de carga com o dobro do volume médio para certificar que o tempo de resposta permanece linear.

Conclusão e Próximos Passos

Salvar 300 ou 3.000 linhas em um banco relacional não deveria levar mais do que poucas centenas de milissegundos. Quando rotinas entram em timeout, o problema quase sempre aponta para padrões de escrita ineficientes que sobrecarregam o MySQL.

Se sua aplicação PHP/MySQL apresenta travamentos recorrentes, timeouts em relatórios ou lentidão ao sincronizar dados, entre em contato para agendar uma consultoria técnica e recuperar a estabilidade do seu sistema.

Preencha o formulário abaixo para que eu consiga entrar em contato com você.