Парсинг больших .csv и добавление в базу

425
13 февраля 2017, 17:29

Есть файл, в нем 300.000 строк, мне нужно обработать каждую строку, и добавть значение в базу. Использую стандартную функцию php

$read = fopen('file.csv', 'r');
while( ($str = fgetcsv($read, 8000, ',')) !== FALSE ):
    //... парсинг файла
endwhile;

Сам файл обрабатывается очень быстро, около 0.6 - 0.9сек.

Но когда добавляю проверку строки, или значения - то скорость увеличивается минимум в 1 раз. Это понятно, что на 300к строк добавить проверку, и это занимает время.

Но потом, еще мне нужно добавить все эти строки в базу, я комбинирую запросы, и отправляю в 1ом запросе по несколько запросов комбинирую.

$sql_add = "UPDATE `table` 
            SET `prof` = '1,4,6,8' 
            WHERE `id` IN(2,3,4,1,1,2233,3321,1,3,2... сюда еще дописывается в среднем до 1000 айдишников)";
$query = Db::query($sql_add);
$query->closeCursor();# не ждать ответа от запроса

И вот благодаря таким комбинированным запросам - кол-во запросов уменьшается. Но все равно, на 100к записей приходит около 1.250запросов, и у меня скрипт виснет на долгое время, вплоть до таймаута.

Как такие большие файлы разбирают, и как их в базу быстро добавлять?.. Если основное время уходит на отправку запросов и проверку данных.

Answer 1

Проблема известная, и не всегда доступны для изменения параметры таймаута на сервере. Для обработки длинных запросов многие плагины (например, резервного копирования) разбивают свой код на куски (chunks), которые перезапускают сами себя для продолжения. Или создают очередь кусков, которые потом выполняются один за другим.

Я делал такое на WordPress для закачки 14 тыс товаров. Не знаю, на чем у Вас сайт, но общий принцип работы один и тот же: запуск кусков из очереди по cron.

Answer 2

Рекомендую сделать импорт файла напрямую в БД посредством LOAD INFILE:

Создаёте стаблицу, подходящую под файл:

CREATE TABLE 'CSVImport' (id INT);
ALTER TABLE CSVImport ADD COLUMN Title VARCHAR(256);
ALTER TABLE CSVImport ADD COLUMN Company VARCHAR(256);
ALTER TABLE CSVImport ADD COLUMN NumTickets VARCHAR(256);

Загружаете:

LOAD DATA INFILE '/home/paul/clientdata.csv' INTO TABLE CSVImport;

А потом делаете вставку в нужную вам таблицу через INSERT INTO (SELECT ... FROM CVSImport)

Answer 3

Могу предложить использовать подготовленные запросы.

$connection->autocommit(false); //Отключам автоматическую фиксацию изменений базы данных при проведении транзакции. И, собственно, начиинаем саму транзакцию
if (!($stmt = $connection->prepare("INSERT INTO orders (id_orders, id_handlings, id_gens, id_gens_groups, id_genotypes, id_staff, consider_cost) VALUES (DEFAULT, '$id_handlings', ?, ?, DEFAULT, DEFAULT, DEFAULT)"))) {die($connection->error);}; //Подготоавливаем запрос
if (!$stmt->bind_param('ii', $id_gens, $id_gens_groups)) {die($connection->error);}; //Привязываем переменные - обе integer => ii
$id_gens=15; //получаем значение одной из переменных
$id_gens_groups=25;
if (!$stmt->execute()) {die($connection->error);}; //Выполняем подготовленный запрос.
if (!$connection->commit()) {die($connection->error);}; //Сохраняем транзакцию

Таким образом БД не будет каждый раз компилировать каждый запрос. А скомпилирует его раз и будет просто подставлять значения. Это должно помочь.

К тому же время работы скрипта (максимальное) можно увеличить или вовсе убрать таймаут

set_time_limit(0); //Устанавливаем максимальное время выполнения скрипта в секундах. Ноль - неограничено.

Вариант ещё один. Скачать БД и поднять её на локальной машине. Провести все манипуляции на локальной машине, создать копию и заменить на сервере.

Answer 4
  1. Входной файл можно обрабатывать параллельно (при наличии такой возможности - несколько процессоров), это в несколько порядков увеличит скорость. То есть несколько потоков формируют входной файл для LOAD DATA INFILE.
  2. Далее действуете как написал @Daniel Protopopov
READ ALSO
mailer yii2 500 error на хостинге

mailer yii2 500 error на хостинге

Добрый день, прошу пнуть на нужный материал или мысльТема такая, что сайт на хостинге и выполнены настройки согласно документации хоста

407
Laravel миграции смена типа таблицы (MyISAM -> MyISAM)

Laravel миграции смена типа таблицы (MyISAM -> MyISAM)

До версии MySQL 56+ тип таблиц MyISAM не поддерживает полнотекстовый поиск

400
Как отозвать все access token-ы конкретного пользователя в laravel passport?

Как отозвать все access token-ы конкретного пользователя в laravel passport?

Допустим у меня есть такая теоретическая ситуация, что мне необходимо отозвать все access token-ы конкретного пользователя, по какой-то его манипуляции

308
Запрос на удаление строки из БД

Запрос на удаление строки из БД

Пишу плагин,сделал все что надо было

363