Avatar ·

Фатальная ошибка: исчерпан допустимый размер памяти 134217728 байт (128 МБ) в PHP

Всем привет! Я новичок в PHP, и у меня возникла проблема. Мой скрипт обрабатывает CSV-файл с данными о товарах — около 500 000 строк. Когда я запускаю его на сервере, через пару минут получаю ошибку: ``` Fatal Error: Allowed Memory Size of 134217728 Bytes Exhausted ``` Я понимаю, что это связано с ограничением памяти в 128 МБ, но не знаю, как это исправить. Вот мой код: ```php ``` Я пробовал увеличить memory_limit в php.ini до 256M, но это не помогло — ошибка появляется снова, только чуть позже. Может быть, я что-то делаю не так? Как правильно обрабатывать большие файлы, чтобы не вылетать за лимит памяти? Подскажите, пожалуйста, как опытные разработчики решают такие проблемы. Заранее спасибо! <<>>

Почему возникает ошибка и как её диагностировать

Ошибка 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.