Головне правило - ніколи не тримати в пам'яті весь файл і не накопичувати результати. Читати рядок за рядком і одразу обробляти.
Читання потоком:
function rows(string $path): Generator
{
$file = new SplFileObject($path);
$file->setFlags(SplFileObject::READ_CSV | SplFileObject::SKIP_EMPTY | SplFileObject::READ_AHEAD);
$header = null;
foreach ($file as $row) {
if ($header === null) {
$header = $row;
continue;
}
yield array_combine($header, $row);
}
}
Генератор віддає по одному рядку, тож пам'ять стала незалежно від розміру файлу.
Запис у базу - порціями:
$batch = [];
foreach (rows('import.csv') as $row) {
$batch[] = ['email' => $row['email'], 'name' => $row['name']];
if (count($batch) === 1000) {
DB::table('subscribers')->insert($batch);
$batch = [];
}
}
if ($batch !== []) {
DB::table('subscribers')->insert($batch);
}
Вставка по одному рядку - мільйон запитів; порціями по 1000 - тисяча. Масова вставка через query builder ще й не створює моделей і не запускає подій.
Що ще враховувати:
- Кодування й роздільник: CSV з Excel часто в Windows-1251 і з
;. Перекодування на льоту - потоковим фільтром (php://filter/read=convert.iconv.windows-1251/utf-8/resource=...). - Логування запитів вимкнене:
DB::enableQueryLog()у довгому імпорті накопичує всі запити в пам'яті. - Помилки в окремих рядках не мають зупиняти весь імпорт - збирати їх у звіт.
- Перервність: запам'ятовувати номер обробленого рядка, щоб продовжити після збою, а не починати спочатку.
- Черга: великий імпорт з веб-інтерфейсу - завдання в черзі, а не синхронний запит.
У Laravel те саме зручно робити через LazyCollection::make(fn () => yield from rows(...))->chunk(1000).