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

Bookmarks da grid do Magento 2: colunas somem e o filtro fica preso

Editou o listing.xml e o admin continua com as colunas antigas ou um filtro travado? O estado da grid está…

16 horas atrás

Unknown module in the enabled modules list: erro no Magento 2

Apagou a pasta do módulo e agora todo bin/magento morre reclamando de módulo desconhecido? A lista continua no app/etc/config.php, e…

2 dias atrás

Ordenar posts por meta_value_num: o preço em ordem certa no WP_Query

Preço gravado em post meta sai fora de ordem porque o MySQL compara texto. Use orderby meta_value_num e inclua quem…

3 dias atrás

Action Scheduler travado no WooCommerce: milhares de ações pendentes

Fila do Action Scheduler com dezenas de milhares de ações pendentes na loja? Rode a fila pelo WP-CLI, limpe o…

3 dias atrás

Query Builder do CodeIgniter 4: ver o SQL que ele realmente gerou

A consulta volta vazia e você não sabe qual WHERE foi montado. Veja o SQL antes de rodar com getCompiledSelect(false)…

4 dias atrás

Base legal da LGPD no checkout: o que é contrato e o que exige consentimento

Pare de pedir checkbox para tudo no checkout: mapeie campo a campo qual base legal do artigo 7º sustenta cada…

5 dias atrás