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

DateTimeImmutable modify: por que a data não muda no PHP 8.3

Trocou DateTime por DateTimeImmutable e a data parou de andar? O modify devolve uma cópia nova em vez de alterar…

12 horas atrás

Defer nos scripts do WordPress 6.9 sem plugin, com wp_enqueue_script

Use o parâmetro strategy do wp_enqueue_script para adiar o JavaScript do tema, sem plugin de otimização, e entenda por que…

2 dias atrás

Banner de cookie que bloqueia de verdade: script só carrega depois do aceite

O banner aparece, o visitante não clica em nada e o Analytics já gravou cookie. Veja como segurar o script…

2 dias atrás

Revisar git diff com IA antes do commit: script que lê só o staged

Var_dump esquecido e senha de teste passam batido no commit. Um script PHP que lê o git diff --staged e…

3 dias atrás

Cannot query field no GraphQL do Magento 2: o campo existe e some

O campo está no schema.graphqls e a API insiste em Cannot query field. Limpe o cache certo, recompile o generated…

4 dias atrás

Block pattern não aparece no inseridor: registre pelo arquivo em /patterns

Padrão registrado no functions.php que não aparece na aba Padrões. O caminho curto do WordPress 6.9: um arquivo PHP na…

4 dias atrás