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.