|
|
|
| Требуется занести в базу информацию из прайс-листа в формате CSV, каждая строка таблицы которого содержит сведения о производителе, артикуле, названии, цене и количестве товара.
Специфика дела состоит в том, что какие-то записи в базе уже есть и в них надо обновить цену и количество. А совсем новые товары только добавить.
Я это делаю абсолютно прямолинейно.
Для каждого товара из прайса ищу в базе товар с таким же производителем, артикулом и описанием. Если такой товар нашелся, то обновляю в нем цену и количество. А если такого товара нет, то добавляю новый товар в базу.
Внести предполагается полтора миллиона товаров партиями по 50 тысяч.
К сожалению, даже первые 50 тысяч закачиваются настолько долго, что хостер сбрасывает скрипт.
Может быть, этот процесс можно организовать как-нибудь получше?
<?php
$prise = file_get_contents("tmp/" . $_FILES['userfile']['name']);
$m_prise = explode(chr(13).chr(10), $prise);
for ($i = 1; $i < count ($m_prise); $i++)
{
unset($m_str); // Удаление массива
$m_str = explode(";", $m_prise[$i]);
$producer = $m_str[0];
$artikul = $m_str[1];
$name_tov = $m_str[2];
$cena = $m_str[3];
$kol = $m_str[4];
$producer = iconv('cp1251', 'utf-8', $producer);
$name_tov = iconv('cp1251', 'utf-8', $name_tov);
// Проверим наличие записей с данным производителем, артикулом и описанием
unset($res); // Удаление
unset($id); // Удаление
$res = @mysql_query("SELECT id FROM kattov WHERE producer = '$producer' AND artikul = '$artikul' AND name_tov = '$name_tov'");
$id = @mysql_result($res, 0, 'id');
if ($id) // Обновляем запись
mysql_query("UPDATE kattov SET cena = '$cena', kol = '$kol' WHERE id = '$id'");
else // Добавляем запись
mysql_query ("INSERT INTO kattov (producer, artikul, name_tov, cena, kol) VALUES ('$producer', '$artikul', '$name_tov', '$cena', '$kol')");
}
|
| |
|
|
|
|
|
|
|
для: Владимир55
(09.11.2012 в 21:01)
| | Вы при первом прогоне их не выполняйте, а сохраняйте в файл. Затем из файла извлекайте 100 запросов и выполняйте и удаляйте их из файла, потом еще 100 запросов, выполняйте и удаляйте. INSERT запросы, кстати, можно сделать многотабличными, только проконтролируйте, чтобы длина запроса не превышала значения директивы max_allowed_packet. Вместо 100 подберите количество запросов эмпирически (иногда это 1000, иногда 25 - от возможностей сервера зависит и от величины таблицы).
PS Как выглядит результат возвращаемый запросом SHOW CREATE TABLE kattov? | |
|
|
|
|
 39.6 Кб |
|
|
для: cheops
(09.11.2012 в 21:27)
| | Правильно ли я понял, что надо сохранить CSV-файл с информацией, а потом из него по сто строк извлекать и информацию из них импортировать?
Или имеется в виду что-то другое?
А что даст этот прием? Хоть партиями, хоть непрерывно - ведь объем операций тот же самый. В чем тут хитрость?
(HostCMS делает именно так - вводит прайс партиями по 100 штук. Но каков алгоритм ввода, мне неизвестно).
==============
А если сформировать дополнительное поле, совместив в него три первый параметра как одно слово? И потом проиндексировать по этому полю?
Тогда при проверке наличия в базе вводимой строки нужно будет выполнять не три условия, а только одно.
Это не будет быстрее?
Скриншот во вложении. Ужать поля уже не удастся. | |
|
|
|
|
|
|
|
для: Владимир55
(09.11.2012 в 22:05)
| | >Правильно ли я понял, что надо сохранить CSV-файл с информацией, а потом из него по сто
>строк извлекать и информацию из них импортировать?
> Или имеется в виду что-то другое?
Другое. Вы разбираете CSV-файл, подготавливая SQL-запросы для обновления. INSERT-запрос в идеале у вас вообще будет один (многостроковый), вы его за один раз вставите, а не будете отправлять тысячи отдельных запросов к базе данных. UPDATE-запросы придется выполнять по одиночке, но у вас процесс будет более масштабируемый, так как они будут уже подготовлены и вам при выполнении скрипта не придется выполнять никаких SELECT-запросов, разбора CSV-файла, можно будет вообще через командную строку обновлять таблицу, минуя PHP (например, утилита mysql выполнит SQL-дамп гораздо быстрее).
>А если сформировать дополнительное поле, совместив в него три первый параметра как одно
>слово? И потом проиндексировать по этому полю?
>Тогда при проверке наличия в базе вводимой строки нужно будет выполнять не три условия, а
>только одно.
>Это не будет быстрее?
Не плохая идея, только лучше её еще усилить и вместо комбинации слов использовать хэш по всем столбцам записи. Если хэш уникальный, вы тогда сможете заранее отбрасывать записи, которые не нуждаются в обновлении, просто проверив наличие такого хэша в таблице. | |
|
|
|
|
|
|
|
для: cheops
(09.11.2012 в 22:23)
| | Очень интересно, спасибо! Даже не подозревал о наличии таких приемов! | |
|
|
|
|
|
|
|
для: Владимир55
(09.11.2012 в 23:00)
| | Интересно, а сколько запросов целесообразно объединять в один многостроковый запрос? Пятьдесят тысяч, сто тысяч, миллион?
Ведь один большой запрос сократит время обращения к базе, но весьма нагрузит хостинг, причем на длительное время. Где оптимум? Или все решается только экспериментально? | |
|
|
|
|
|
|
|
для: Владимир55
(10.11.2012 в 00:04)
| | А там что, PMA нету? Он умеет сам разбивать дамп при приблежении критического времени выполнения скрипта.
Подготовьте дамп локально, тем же пхп перелопатьте его из csv в sql с многострочными инсертами длинной ~50000 символов, заархивируйте и закиньте в импорт PMA. Просто будете ему нажимать продолжить при разрывах.
Вместо проверки наличия строк в базе, можно использовать конструкцию INSERT ... ON DUPLICATE KEY UPDATE.
Правда для этого придется временно добавить составной уникальный индекс на 3 поля: producer, artikul и name_tov.... Но это, думаю, будет быстрее, чем 1,5 миллиона селектов. | |
|
|
|
|
|
|
|
для: Sfinks
(10.11.2012 в 01:03)
| | "А там что, PMA нету? Он умеет сам разбивать дамп при приблежении критического времени выполнения скрипта."
Можете пояснить эту мысль?
Что такое PMA и где он должен быть, что делать? | |
|
|
|
|
|
|
|
для: Владимир55
(10.11.2012 в 12:18)
| | PMA - PhpMyAdmin. Думаю Вы знаете что это. Просто аббревиатуру не поняли =) | |
|
|
|
|
|
|
|
для: Владимир55
(10.11.2012 в 00:04)
| | >Интересно, а сколько запросов целесообразно объединять в один многостроковый запрос? Пятьдесят тысяч, сто тысяч, миллион?
Вы упретесь в размер одного запроса, который определяется max_allowed_packet (лучше посмотреть сколько он у вас), как правило, от 2 до 32Мб. При формировании запроса, подсчитывайте его размер, как только он приблизится к max_allowed_packet, начинайте формировать второй. | |
|
|
|
|
|
|
|
для: cheops
(10.11.2012 в 12:15)
| | Подсчет я могу вести только в количестве знаков, а max_allowed_packet задан в байтах.
Как сопоставить эти значения?
Можно ли считать, что 1 знак = два байта (запрос в юникоде)?
Тогда вес запроса
$ves_SQL = 2 * strlen($sql);
|
То есть, при значении max_allowed_packet = 16М можно исполнять запрос размером 8388606 знаков.
Так? | |
|
|
|
|
|
|
|
для: Владимир55
(10.11.2012 в 12:19)
| | Если у вас директива mbstring.func_overload не установлена в значение 2, то strlen() возвращает количество байт, т.е. можно использовать результат как есть для сравнения с max_allowed_packet. В этом и проблема PHP, что стандартные функции не работают корректно со стандартными кодировками. Если же у вас mbstring.func_overload установлен таким образом, что осуществляется перехват вызовов стандартных строковых функций и вызов mb_string аналогов, тогда да, нужно умножать на 2 (в случае русской кодировки). | |
|
|
|
|
|
|
|
для: cheops
(10.11.2012 в 13:04)
| | Помимо оптимизации запросов, хостер предлагает еще осуществить закачку по частям, поскольку на хостинге установлено максимально время обработки (600 секунд).
Это легко реализовать вручную, разбив CSV файл на части. Но так неинтерено...
А как закачать большой CSV файл, но чтобы для хостинга его исполнение было как серия независимых коротких закачек? | |
|
|
|
|
|
|
|
для: Владимир55
(10.11.2012 в 13:46)
| | Наладить сron-задание, которое будет ожидать файл в определенной папке или запустить таймер на JavaScript. | |
|
|
|
|
|
|
|
для: cheops
(10.11.2012 в 14:29)
| | А какого интервала времени достаточно для того, чтобы хостинг посчитал это задание новым?
Ведь не обязательно ждать целую секнуду? | |
|
|
|
|
|
|
|
для: Владимир55
(10.11.2012 в 19:58)
| | В принципе любое задание будет новым, так как будет запускаться новый процесс/поток (в зависимости от операционной системы), а по времени и ресурсам ограничиваются именно процессы/потоки. | |
|
|
|
|
 6.7 Кб |
|
|
для: cheops
(11.11.2012 в 08:41)
| | Я тоже предположил, что достаточно смены УРЛа. А если так, то можно использовать два файла с закольцованным редиректом.
В первом файле:
<?php
Список исполняемых кодов.
...
...
$no - номер цикла
header('Location: file-2.php?no=$no);
exit;
|
Во втором файле
<?php
$no = $_GET['no']++;
header('Location: file-1.php?no=$no);
exit;
|
Это корректно?
==================
Во вложении скриншот нагрузки на хостинг при импорте большого прайса в HostCMS. Можно увидеть, что закачка длится больше двух часов и нагрузка вдвое превышает все допустимые нормы, однако хостинг этот процесс не блокирует.
А в Битрексе хостинг обрывает загрузку ровно через 10 минут, поскольку у хостинга лимит на процесс 600 секунд, а процесс не дробится... | |
|
|
|
|
|
|
|
для: Владимир55
(11.11.2012 в 14:04)
| | В общем корректно, но не очень надежно, лучше запустить на JavaScript таймер, я кстати, что-то такое и на Bitrix видел (особенно, при формировании резервных копий). На парсинге такого может и не быть, более того, тоже сталкивался с тем, что Bitrix не успевает развернуть резервную копию из-за ограничений по времени. Обычно забрасываю архив прямо на этот же хост, указываю загрузку с удаленного хоста и подставляю http-адрес архива, в результате чего Bitrix очень быстро его загружает и успевает развернуть. | |
|
|
|
|
|
|
|
для: cheops
(11.11.2012 в 18:31)
| | "не очень надежно"
Вот и у меня такое же ощущение - именно, что ненадежно: то ли сработает, то ли нет. Хотя, по идее, чем этот оператор отличается от других?
Почему это он может не сработать? | |
|
|
|
|
|
|
|
для: Владимир55
(11.11.2012 в 19:52)
| | Данные буферизуются, если не будет долго ответа от сервера, браузер может решить, что связь прервалась... не будет больше браузер грузить страницу и связь прервалась, если же вы загрузили страницу и запустили JS-таймер - он будет дергать сервер до потери пульса, хоть несколько дней подряд. | |
|
|
|