Форум: Форум PHPФорум ApacheФорум Регулярные ВыраженияФорум MySQLHTML+CSS+JavaScriptФорум FlashРазное
Новые темы: 0000000
MySQL 5. В подлиннике. Авторы: Кузнецов М.В., Симдянов И.В. Социальная инженерия и социальные хакеры. Авторы: Кузнецов М.В., Симдянов И.В. PHP 5/6. В подлиннике. Авторы: Кузнецов М.В., Симдянов И.В. C++. Мастер-класс в задачах и примерах. Авторы: Кузнецов М.В., Симдянов И.В. Самоучитель MySQL 5. Авторы: Кузнецов М.В., Симдянов И.В.
ВСЕ НАШИ КНИГИ
Консультационный центр SoftTime

Разное

Выбрать другой форум

 

Здравствуйте, Посетитель!

вид форума:
Линейный форум Структурный форум

тема: Можно ли сделать алгоритм поумнее?
 
 автор: Владимир55   (09.11.2012 в 21:01)   письмо автору
 
 

Требуется занести в базу информацию из прайс-листа в формате 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($res0'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')");

    }

  Ответить  
 
 автор: cheops   (09.11.2012 в 21:27)   письмо автору
 
   для: Владимир55   (09.11.2012 в 21:01)
 

Вы при первом прогоне их не выполняйте, а сохраняйте в файл. Затем из файла извлекайте 100 запросов и выполняйте и удаляйте их из файла, потом еще 100 запросов, выполняйте и удаляйте. INSERT запросы, кстати, можно сделать многотабличными, только проконтролируйте, чтобы длина запроса не превышала значения директивы max_allowed_packet. Вместо 100 подберите количество запросов эмпирически (иногда это 1000, иногда 25 - от возможностей сервера зависит и от величины таблицы).

PS Как выглядит результат возвращаемый запросом SHOW CREATE TABLE kattov?

  Ответить  
 
 автор: Владимир55   (09.11.2012 в 22:05)   письмо автору
39.6 Кб
 
   для: cheops   (09.11.2012 в 21:27)
 

Правильно ли я понял, что надо сохранить CSV-файл с информацией, а потом из него по сто строк извлекать и информацию из них импортировать?
Или имеется в виду что-то другое?

А что даст этот прием? Хоть партиями, хоть непрерывно - ведь объем операций тот же самый. В чем тут хитрость?

(HostCMS делает именно так - вводит прайс партиями по 100 штук. Но каков алгоритм ввода, мне неизвестно).
==============

А если сформировать дополнительное поле, совместив в него три первый параметра как одно слово? И потом проиндексировать по этому полю?
Тогда при проверке наличия в базе вводимой строки нужно будет выполнять не три условия, а только одно.
Это не будет быстрее?

Скриншот во вложении. Ужать поля уже не удастся.

  Ответить  
 
 автор: cheops   (09.11.2012 в 22:23)   письмо автору
 
   для: Владимир55   (09.11.2012 в 22:05)
 

>Правильно ли я понял, что надо сохранить CSV-файл с информацией, а потом из него по сто
>строк извлекать и информацию из них импортировать?
> Или имеется в виду что-то другое?
Другое. Вы разбираете CSV-файл, подготавливая SQL-запросы для обновления. INSERT-запрос в идеале у вас вообще будет один (многостроковый), вы его за один раз вставите, а не будете отправлять тысячи отдельных запросов к базе данных. UPDATE-запросы придется выполнять по одиночке, но у вас процесс будет более масштабируемый, так как они будут уже подготовлены и вам при выполнении скрипта не придется выполнять никаких SELECT-запросов, разбора CSV-файла, можно будет вообще через командную строку обновлять таблицу, минуя PHP (например, утилита mysql выполнит SQL-дамп гораздо быстрее).

>А если сформировать дополнительное поле, совместив в него три первый параметра как одно
>слово? И потом проиндексировать по этому полю?
>Тогда при проверке наличия в базе вводимой строки нужно будет выполнять не три условия, а
>только одно.
>Это не будет быстрее?
Не плохая идея, только лучше её еще усилить и вместо комбинации слов использовать хэш по всем столбцам записи. Если хэш уникальный, вы тогда сможете заранее отбрасывать записи, которые не нуждаются в обновлении, просто проверив наличие такого хэша в таблице.

  Ответить  
 
 автор: Владимир55   (09.11.2012 в 23:00)   письмо автору
 
   для: cheops   (09.11.2012 в 22:23)
 

Очень интересно, спасибо! Даже не подозревал о наличии таких приемов!

  Ответить  
 
 автор: Владимир55   (10.11.2012 в 00:04)   письмо автору
 
   для: Владимир55   (09.11.2012 в 23:00)
 

Интересно, а сколько запросов целесообразно объединять в один многостроковый запрос? Пятьдесят тысяч, сто тысяч, миллион?

Ведь один большой запрос сократит время обращения к базе, но весьма нагрузит хостинг, причем на длительное время. Где оптимум? Или все решается только экспериментально?

  Ответить  
 
 автор: Sfinks   (10.11.2012 в 01:03)   письмо автору
 
   для: Владимир55   (10.11.2012 в 00:04)
 

А там что, PMA нету? Он умеет сам разбивать дамп при приблежении критического времени выполнения скрипта.

Подготовьте дамп локально, тем же пхп перелопатьте его из csv в sql с многострочными инсертами длинной ~50000 символов, заархивируйте и закиньте в импорт PMA. Просто будете ему нажимать продолжить при разрывах.

Вместо проверки наличия строк в базе, можно использовать конструкцию INSERT ... ON DUPLICATE KEY UPDATE.
Правда для этого придется временно добавить составной уникальный индекс на 3 поля: producer, artikul и name_tov.... Но это, думаю, будет быстрее, чем 1,5 миллиона селектов.

  Ответить  
 
 автор: Владимир55   (10.11.2012 в 12:18)   письмо автору
 
   для: Sfinks   (10.11.2012 в 01:03)
 

"А там что, PMA нету? Он умеет сам разбивать дамп при приблежении критического времени выполнения скрипта."

Можете пояснить эту мысль?

Что такое PMA и где он должен быть, что делать?

  Ответить  
 
 автор: Sfinks   (10.11.2012 в 14:43)   письмо автору
 
   для: Владимир55   (10.11.2012 в 12:18)
 

PMA - PhpMyAdmin. Думаю Вы знаете что это. Просто аббревиатуру не поняли =)

  Ответить  
 
 автор: cheops   (10.11.2012 в 12:15)   письмо автору
 
   для: Владимир55   (10.11.2012 в 00:04)
 

>Интересно, а сколько запросов целесообразно объединять в один многостроковый запрос? Пятьдесят тысяч, сто тысяч, миллион?
Вы упретесь в размер одного запроса, который определяется max_allowed_packet (лучше посмотреть сколько он у вас), как правило, от 2 до 32Мб. При формировании запроса, подсчитывайте его размер, как только он приблизится к max_allowed_packet, начинайте формировать второй.

  Ответить  
 
 автор: Владимир55   (10.11.2012 в 12:19)   письмо автору
 
   для: cheops   (10.11.2012 в 12:15)
 

Подсчет я могу вести только в количестве знаков, а max_allowed_packet задан в байтах.

Как сопоставить эти значения?

Можно ли считать, что 1 знак = два байта (запрос в юникоде)?

Тогда вес запроса

$ves_SQL = 2 * strlen($sql); 


То есть, при значении max_allowed_packet = 16М можно исполнять запрос размером 8388606 знаков.

Так?

  Ответить  
 
 автор: cheops   (10.11.2012 в 13:04)   письмо автору
 
   для: Владимир55   (10.11.2012 в 12:19)
 

Если у вас директива mbstring.func_overload не установлена в значение 2, то strlen() возвращает количество байт, т.е. можно использовать результат как есть для сравнения с max_allowed_packet. В этом и проблема PHP, что стандартные функции не работают корректно со стандартными кодировками. Если же у вас mbstring.func_overload установлен таким образом, что осуществляется перехват вызовов стандартных строковых функций и вызов mb_string аналогов, тогда да, нужно умножать на 2 (в случае русской кодировки).

  Ответить  
 
 автор: Владимир55   (10.11.2012 в 13:46)   письмо автору
 
   для: cheops   (10.11.2012 в 13:04)
 

Помимо оптимизации запросов, хостер предлагает еще осуществить закачку по частям, поскольку на хостинге установлено максимально время обработки (600 секунд).

Это легко реализовать вручную, разбив CSV файл на части. Но так неинтерено...

А как закачать большой CSV файл, но чтобы для хостинга его исполнение было как серия независимых коротких закачек?

  Ответить  
 
 автор: cheops   (10.11.2012 в 14:29)   письмо автору
 
   для: Владимир55   (10.11.2012 в 13:46)
 

Наладить сron-задание, которое будет ожидать файл в определенной папке или запустить таймер на JavaScript.

  Ответить  
 
 автор: Владимир55   (10.11.2012 в 19:58)   письмо автору
 
   для: cheops   (10.11.2012 в 14:29)
 

А какого интервала времени достаточно для того, чтобы хостинг посчитал это задание новым?

Ведь не обязательно ждать целую секнуду?

  Ответить  
 
 автор: cheops   (11.11.2012 в 08:41)   письмо автору
 
   для: Владимир55   (10.11.2012 в 19:58)
 

В принципе любое задание будет новым, так как будет запускаться новый процесс/поток (в зависимости от операционной системы), а по времени и ресурсам ограничиваются именно процессы/потоки.

  Ответить  
 
 автор: Владимир55   (11.11.2012 в 14:04)   письмо автору
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 секунд, а процесс не дробится...

  Ответить  
 
 автор: cheops   (11.11.2012 в 18:31)   письмо автору
 
   для: Владимир55   (11.11.2012 в 14:04)
 

В общем корректно, но не очень надежно, лучше запустить на JavaScript таймер, я кстати, что-то такое и на Bitrix видел (особенно, при формировании резервных копий). На парсинге такого может и не быть, более того, тоже сталкивался с тем, что Bitrix не успевает развернуть резервную копию из-за ограничений по времени. Обычно забрасываю архив прямо на этот же хост, указываю загрузку с удаленного хоста и подставляю http-адрес архива, в результате чего Bitrix очень быстро его загружает и успевает развернуть.

  Ответить  
 
 автор: Владимир55   (11.11.2012 в 19:52)   письмо автору
 
   для: cheops   (11.11.2012 в 18:31)
 

"не очень надежно"

Вот и у меня такое же ощущение - именно, что ненадежно: то ли сработает, то ли нет. Хотя, по идее, чем этот оператор отличается от других?

Почему это он может не сработать?

  Ответить  
 
 автор: cheops   (11.11.2012 в 20:15)   письмо автору
 
   для: Владимир55   (11.11.2012 в 19:52)
 

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

  Ответить  
Rambler's Top100
вверх

Rambler's Top100 Яндекс.Метрика Яндекс цитирования