Фатальная ошибка: исчерпан допустимый размер памяти 134217728 байт (128 МБ) в PHP
Почему возникает ошибка и как её диагностировать
Ошибка Allowed Memory Size Exhausted означает, что PHP-скрипт пытается использовать больше оперативной памяти, чем разрешено директивой memory_limit. В вашем случае 128 МБ — это стандартное ограничение для многих хостингов. Проблема в том, что вы загружаете весь CSV-файл в массив $products. При 500 000 строк с 10-20 полями в каждой этот массив легко занимает 200-500 МБ и более. Увеличение лимита до 256 МБ лишь отсрочивает ошибку, но не решает её коренным образом.
Чтобы узнать текущий расход памяти, используйте функции:
echo memory_get_usage(true) . "\n"; // в байтах
echo memory_get_peak_usage(true) . "\n"; // пиковое значение
Основное решение: потоковая обработка (итераторы)
Вместо загрузки всех данных в память, обрабатывайте файл построчно. PHP предоставляет отличный инструмент — класс SplFileObject с итератором CSV. Вот исправленный код:
<?php
$file = new SplFileObject('products.csv');
$file->setFlags(SplFileObject::READ_CSV);
foreach ($file as $row) {
if ($row === [null]) continue; // пропускаем пустые строки
// Обрабатываем каждую строку сразу
$productName = $row[0] ?? '';
$price = $row[1] ?? 0;
// Например, записываем в БД или другой файл
echo "Обработан товар: $productName\n";
// Если нужно накопить только часть данных — используйте лимит
}
?>
Преимущества:
- В памяти хранится только одна строка за раз (несколько килобайт вместо сотен мегабайт)
- Нет риска превысить memory_limit даже для файлов в 10+ ГБ
- Код работает быстрее, так как не тратит время на выделение огромного массива
Если нужно сохранить данные для повторного использования
Вместо массива используйте базу данных (SQLite, MySQL) или временные файлы:
<?php
$db = new SQLite3(':memory:'); // или файл: 'temp.db'
$db->exec('CREATE TABLE products (name TEXT, price REAL)');
$file = new SplFileObject('products.csv');
$file->setFlags(SplFileObject::READ_CSV);
$stmt = $db->prepare('INSERT INTO products VALUES (:name, :price)');
foreach ($file as $row) {
$stmt->bindValue(':name', $row[0] ?? '', SQLITE3_TEXT);
$stmt->bindValue(':price', (float)($row[1] ?? 0), SQLITE3_FLOAT);
$stmt->execute();
}
// Теперь работаем с БД, а не с массивом
$result = $db->query('SELECT * FROM products');
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
echo $row['name'] . "\n";
}
?>
Дополнительные советы
- Очищайте ресурсы: после обработки больших данных вызывайте
unset()для переменных иgc_collect_cycles()для принудительного запуска сборщика мусора. - Используйте yield (генераторы): если вы пишете свою библиотеку, создавайте функции-генераторы, которые возвращают данные по одному элементу.
- Настройте memory_limit разумно: для обычных скриптов хватит 128-256 МБ, но для пакетной обработки лучше использовать потоковый подход, а не увеличение лимита.
- Проверяйте утечки памяти: если память растёт внутри цикла — ищите замыкания, статические переменные или глобальные массивы, которые не очищаются.
Помните: увеличение memory_limit — это костыль, а не решение. Правильная архитектура подразумевает минимальное потребление памяти. Ваш случай — классический пример, когда "жадный" алгоритм (загрузка всего сразу) заменяется на "ленивый" (обработка по требованию).
<<Альтернативный подход: разбивка на чанки и увеличение лимита только для CLI
Если по каким-то причинам вы не можете переписать код на потоковую обработку (например, legacy-проект, где данные нужны целиком для сложной сортировки), можно использовать компромиссный вариант — обрабатывать файл частями (chunks).
<?php
$chunkSize = 1000; // обрабатываем по 1000 строк
$file = fopen('products.csv', 'r');
$chunk = [];
$counter = 0;
while (($line = fgetcsv($file)) !== false) {
$chunk[] = $line;
$counter++;
if ($counter % $chunkSize === 0) {
processChunk($chunk); // ваша функция обработки
$chunk = []; // очищаем память
}
}
// Обработка остатка
if (!empty($chunk)) {
processChunk($chunk);
}
fclose($file);
function processChunk(array $data) {
foreach ($data as $row) {
// обработка
}
// После завершения функции память освободится автоматически
}
?>
Этот метод позволяет контролировать пиковое потребление памяти. Увеличьте memory_limit в php.ini или прямо в скрипте:
ini_set('memory_limit', '512M'); // только если действительно нужно
Важно: для CLI-скриптов (командная строка) часто нет ограничения памяти, но на хостинге с shared-окружением лучше не злоупотреблять. Всегда стремитесь к потоковой обработке — это золотой стандарт для больших данных в PHP.