PHP

array_chunk no PHP: processar 50 mil registros em lotes sem estourar memória

O script de importação roda dois minutos e morre com Fatal error: Allowed memory size of 134217728 bytes exhausted. O culpado quase nunca é o INSERT: é o array de 50 mil linhas vivo na memória enquanto o laço acumula objetos. O primeiro corte é array_chunk: ele fatia o trabalho em blocos fixos e deixa você fechar transação a cada bloco.

Lotes de 500 com transação por bloco

<?php
$pdo = new PDO('mysql:host=127.0.0.1;dbname=loja;charset=utf8mb4', $usuario, $senha);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->prepare('INSERT INTO produtos (sku, nome, preco) VALUES (?, ?, ?)');

foreach (array_chunk($linhas, 500) as $i => $lote) {
    $pdo->beginTransaction();
    try {
        foreach ($lote as $linha) {
            $stmt->execute([$linha[0], $linha[1], $linha[2]]);
        }
        $pdo->commit();
    } catch (PDOException $e) {
        $pdo->rollBack();
        throw $e;
    }

    printf("lote %d | pico %.1f MBn", $i, memory_get_peak_usage(true) / 1048576);
}

Uma transação por lote, no lugar de uma por linha, corta o vai e vem com o MySQL 8 sem prender a tabela o script inteiro. O rollBack não é enfeite: um SKU duplicado no meio do bloco deixaria a transação pendurada e a tabela num estado indefinido. Aponte para uma cópia do banco na primeira rodada — 50 mil linhas não têm Ctrl+Z. O printf denuncia o pico subindo a cada volta, sinal de que sobrou coisa na memória.

Quando nem o array cabe

O limite que confunde: array_chunk não reduz o pico da carga inicial. Ele não duplica o conteúdo das strings, mas monta uma tabela nova para cada fatia enquanto o original continua em pé. Se o estouro vem antes do laço, montando $linhas, nenhum tamanho de lote salva. Aí o caminho é nunca ter o array completo:

<?php
function lerLotes(string $arquivo, int $tamanho = 500): Generator
{
    $fh = fopen($arquivo, 'r');
    $lote = [];
    while (($linha = fgetcsv($fh)) !== false) {
        $lote[] = $linha;
        if (count($lote) === $tamanho) {
            yield $lote;
            $lote = [];
        }
    }
    if ($lote !== []) {
        yield $lote;
    }
    fclose($fh);
}

foreach (lerLotes('/var/tmp/produtos.csv') as $lote) {
    // mesmo INSERT do bloco acima
}

O generator devolve 500 linhas por vez e o resto do CSV fica em disco: o consumo não sobe com o arquivo. Rode com php -d memory_limit=256M importar.php. Se passar, resolveu; se subir o limite até funcionar, só empurrou o problema pro arquivo do mês que vem.

Post Recentes

Erro 500 no Magento 2: achar o relatório em var/report pelo ID da tela

O hash que o Magento mostra junto do erro é o nome de um arquivo em var/report. Veja como abrir,…

23 horas atrás

Campo extra no checkout em blocos do WooCommerce sem voltar pro shortcode

Seu snippet com woocommerce_checkout_fields parou de aparecer no checkout em blocos. Registre o campo pela API nova e leia o…

2 dias atrás

Fuso horário no PHP 8.3: log com 3 horas de diferença no servidor

O pedido entrou às 21h e o relatório mostra 00h do dia seguinte. Onde setar date.timezone no PHP 8.3 e…

2 dias atrás

Magento 2 não envia e-mail de pedido: async sending e cron parado

O pedido entra na loja e o e-mail nunca sai: antes de culpar o SMTP, veja o envio assíncrono, a…

3 dias atrás

cron_schedules WordPress: intervalo de 5 minutos no WP-Cron

O WordPress só oferece hourly, twicedaily, daily e weekly. Use o filtro cron_schedules para criar um intervalo de 5 minutos…

4 dias atrás

E-mails do WooCommerce não chegam: como testar o envio de um pedido real

Dispare pela linha de comando o e-mail de um pedido que já existe na loja e descubra se a falha…

4 dias atrás