За последние 24 часа нас посетили 18170 программистов и 1648 роботов. Сейчас ищут 919 программистов ...

Оптимизация построения списка

Тема в разделе "PHP для новичков", создана пользователем Хулиган, 4 апр 2008.

Статус темы:
Закрыта.
  1. Хулиган

    Хулиган Активный пользователь

    С нами с:
    4 апр 2008
    Сообщения:
    13
    Симпатии:
    0
    Написал код, который составляет прайс-лист товаров сгруппированный по категориям.
    Вложенность категорий - произвольная. Категории имеют парентов. Если категория корневая, то у неё парент равен нулю.
    Обычная древовидная структура.
    В каждой из категорий, кроме имеющей id=0, могут содержатся товары.
    Вот собственно код:

    PHP:
    1. $result = mysql_query("SELECT categoryID, name FROM `SS_categories` WHERE parent = 0 ORDER BY name");
    2. while ( $cats = mysql_fetch_row($result) ){
    3.     /* название текущей категории */
    4.     print "<tr><td>$cats[1]</td></tr>\n";
    5.  
    6.     /* перечисление товаров в текущей категории */
    7.     $roots = mysql_query("SELECT categoryID, productID, name, price, count FROM `SS_products` WHERE categoryID = $cats[0] AND enabled = 1 ORDER BY name");
    8.     while ( $rootProds = mysql_fetch_row($roots) ){
    9.         /* вывод названия товара, цены и его наличия */
    10.         if ( $rootProds[4] ) $cnt = "+";
    11.         else $cnt = "-";
    12.         print  "<tr>\n";
    13.         print  "<td>$rootProds[2]</td>\n";
    14.         print  "<td>$rootProds[3]</td>\n";
    15.         print  "<td>$cnt</td>\n";
    16.         print  "</tr>\n";
    17.     }
    18.     /* получение всего списка товаров */
    19.     $res = mysql_query("SELECT categoryID, productID, name, price, count FROM `SS_products` WHERE enabled = 1 ORDER BY name");
    20.     while ( $prods = mysql_fetch_row($res) ){
    21.         /* проверка принадлежности товаров к текущей категории */
    22.         if ( is_embedded($cats[0], $prods[0]) ){
    23.             if ( $prods[4] ) $cnt = "+";
    24.             else $cnt = "-";
    25.             print  "<tr>\n";
    26.             print  "<td>$prods[2]</td>\n";
    27.             print  "<td>$prods[3]</td>\n";
    28.             print  "<td>$cnt</td>\n";
    29.             print  "</tr>\n";
    30.         }
    31.     }
    32. }
    33.  
    34. /* ф-ция определения принадлежности подкатегории child категории root */
    35. function is_embedded ($root, $child){
    36.     $r = mysql_query("SELECT parent FROM `SS_categories` WHERE categoryID = $child") or die(mysql_error());
    37.     if ( $row = mysql_fetch_row($r) ){
    38.         if ( $root==$row[0] ) return true;
    39.         if ( 0 == $row[0] ) return false;
    40.         return is_embedded ($root, $row[0]);
    41.     }
    42. }
    43.  
    Код работает безошибочно. Но есть одна проблема. Состоит в следующем:
    Тестировал на трех серверах: hoster.ru, jino-net.ru и localhost(denver).
    На первом хостинге код тормозит безбожно. Время выполнения составляет в лучшем случае 5 сек. В худшем - генерация страницы прерывается с сообщением об ошибке превышения тайм-лимита (30 сек).
    На втором и третьем серверах код отрабатывает за время менее 0.2сек. Сама база маленькая: количество категорий в таблице - 38, количество товаров - тоже 38.
    Могут ли быть здесь в коде какие-то проблемы, которые вкупе с характеристиками хостера дают такой результат? Я вижу одну неоптимальность, связаную с выполнением в цикле запроса по получению всего списка товаров, но способа оптимизировать это не вижу.

    Связаны ли тормоза при загрузке с hoster.ru с неоптимальностью кода, или это уже проблемы с хостингом?

    P.S.
    Мне кажется, что проблема именно в php-составляющей, а не в mysql, т.к. если я захожу на сервер из утилиты mysql.exe, то никаких тормозов с запросами нет.

    Спасибо.
     
  2. RomanBush

    RomanBush Активный пользователь

    С нами с:
    5 дек 2007
    Сообщения:
    798
    Симпатии:
    0
    Адрес:
    200 км от Москвы
    ShopScript ? Который на Smarty.
    Это весь код на странице или только твой, обёрнутый в шаблон смарти? Это я к тому спрашиваю, что - уверен ли ты, что именно твой кусок тормозит? Если уверен - тогда это, скорее всего, проблемы с хостингом.
    Как вариант - попробуй померять, какой именно кусок тормозит.

    PHP:
    1. <?
    2. $time_start = microtime(1);
    3.  
    4. // Кусок кода
    5.  
    6. $time_end = microtime(1);
    7. $time = $time_end - $time_start;
    8.  
    9. echo "Кусок кода работал $time секунд\n";
    10. ?>
     
  3. Sergey89

    Sergey89 Активный пользователь

    С нами с:
    4 янв 2007
    Сообщения:
    4.796
    Симпатии:
    0
    Да тут и так видно, чего тормозит. Выбираешь все! данные из таблицы, да ещё и сортируешь их по текстовому полю.
    Потом для каждого! продукта ты делаешь ещё один запрос
    А ты посчитай сколько запросов на странице выходит?

    Решение одно. Перепиши код заного.
     
  4. Хулиган

    Хулиган Активный пользователь

    С нами с:
    4 апр 2008
    Сообщения:
    13
    Симпатии:
    0
    Нет, это не shop script, и не smarty. Код полностью свой, никаких шаблонов. Структура БД чем-то напоминает ShopScript, но не повторяет полностью.
    Измерял время работы именно этого фрагмента (который был приведен).

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

    По запросам в функции is_embedded, выполняющимся рекурсивно:
    Как отследить принадлежность (вложенность) одной категории к другой без рекурсии? Если бы список категорий был бы не древовидный, а плоский, никаких проблем не было бы. А отказаться от древовидного списка я не могу.
     
  5. topas

    topas Активный пользователь

    С нами с:
    16 авг 2006
    Сообщения:
    2.258
    Симпатии:
    36
    Приведите, пожалуйста, фрагмента списка и в двух словах объясните что Вы с ним хотите сделать.
     
  6. Хулиган

    Хулиган Активный пользователь

    С нами с:
    4 апр 2008
    Сообщения:
    13
    Симпатии:
    0
  7. topas

    topas Активный пользователь

    С нами с:
    16 авг 2006
    Сообщения:
    2.258
    Симпатии:
    36
    Хулиган
    Спасибо за информацию.

    Мое решение имеет следующий вид:
    1. Достаточно выполнить один SQL запрос ("код, который составляет прайс-лист товаров"), т.к. у нас нет ограничения на категории (кроме parent = 0)
    2. Обработать данные. Нужно получить массив вида
    PHP:
    1. <?php
    2. $category = array(
    3.     0   => array(
    4.         'Автомобили'  => array(
    5.             'Opel'          => true,
    6.             'Porshe'        => true,
    7.             'Mercedes'      => true
    8.         ),
    9.         'Автоэлектроника'    => array(
    10.             'Блоки управления зажиганием'  => array(
    11.                 'ВАЗ'            => true,
    12.                 'ГАЗ'            => true
    13.             )
    14.         )
    15.     )
    16. );
    17.  
    3. Затем выбрать все продукты (SELECT * FROM product)
    4. Выполнить цикл по всем продуктам

    Итого: два SQL запроса, один блок формирования дерева, один - формирования товаров

    PS. Ассоциативнй массив $category можно представить в виде обыкновенного массива, где ключи будут строится исходя из category_id
     
  8. Хулиган

    Хулиган Активный пользователь

    С нами с:
    4 апр 2008
    Сообщения:
    13
    Симпатии:
    0
    Может я не совсем понял идею, но мне кажется, что для построения такого массива мне никуда не деться от рекурсии, аналогичной рекурсивной ф-ции is_embedded. Фактически я избавлюсь от множественных вызовов ф-ции is_embedded, заменив их однократной рекурсией по составлению массива. Но зато потом я вынужден буду ввести ещё одну рекурсию по обходу построенного массива. Т.е. предлагается замена 38 маленьких рекурсий на две больших (одна по составлению массива, вторая по его анализу).
    Или это не так?
     
  9. topas

    topas Активный пользователь

    С нами с:
    16 авг 2006
    Сообщения:
    2.258
    Симпатии:
    36
    нет, не так, на первый взгляд все обходится вообще без рекурсий

    ... использующих sql-запросы :)

    Тут самое сложное: алгоритм построения дерева. Если Вам нужно достать единичную категорию, например "Opel", то действительно, можно будет использовать рекурсию, при этом одну рекурсию... is_embedded()
    Но если Вы делаете полный прайс лист ( о чем собственно говорится в задании ), то необходимость в is_embedded() отпадает

    Попробуйте развить мою мысль и задаться вопросами:
    А можем ли мы в каждом узле хранить информацию о родительском дереве?
    А можем ли мы хранить информацию родительского id в БД?
    А можем ли мы построить дерево таким образом, чтобы поиск по дереву был максимально прост.
     
  10. Хулиган

    Хулиган Активный пользователь

    С нами с:
    4 апр 2008
    Сообщения:
    13
    Симпатии:
    0
    Да, пожалуй хранение родительского id в базе - это хорошая идея.
    Дерево целиком мне не нужно, достаточно знать id самого старшего предка данного узла.
    Можно добавить в таблицу SS_categories поле, содержащее id самого старшего узла (у которого парент=0).
    Необходимость в is_embedded исчезает. Исчезает также необходимость в построении таких жутких массивов (чего я очень боюсь) :)
    При этом нужно при добавлении новой категории в БД вычислить корневой узел, что будет делаться один раз и проблем со временем никаких.

    Попробую сделать так и посмотрю на скорость выполнения.

    P.S.
    наверное даже лучше корневой id прописать не категории, а товару.
    При этом делается один запрос на все товары order by major_id
    И всё.
     
  11. topas

    topas Активный пользователь

    С нами с:
    16 авг 2006
    Сообщения:
    2.258
    Симпатии:
    36
    Не забудьте, пожалуйста, сообщить о результате. С участком кода, конечно.

    PS> искренне за Вас рад. :)
     
  12. armadillo

    armadillo Активный пользователь

    С нами с:
    6 апр 2007
    Сообщения:
    2.380
    Симпатии:
    0
    Адрес:
    Russia, Moscow
    лучше отдельно родитель плюс весь путь доверху.
     
  13. Xerk

    Xerk Активный пользователь

    С нами с:
    5 окт 2007
    Сообщения:
    177
    Симпатии:
    0
    Адрес:
    Владивосток
    насчет дерева... сдесь же на форуме наткнулся на любопытную ссылку, рекомендую ознакомиться... http://getinfo.ru/article610.html
     
  14. armadillo

    armadillo Активный пользователь

    С нами с:
    6 апр 2007
    Сообщения:
    2.380
    Симпатии:
    0
    Адрес:
    Russia, Moscow
    Xerk чаще можно до этого не доводить.
     
  15. Хулиган

    Хулиган Активный пользователь

    С нами с:
    4 апр 2008
    Сообщения:
    13
    Симпатии:
    0
    Очень даже недурственно получилось. Каждому продукту указал его корневую категорию majorID.
    Более чем 1800 запросов к БД свелись к 13 запросам (один на построение списка корневых + по одному запросу для каждого корня):

    PHP:
    1. <? php
    2. $roots_arr = mysql_query("SELECT categoryID, name FROM `SS_categories` WHERE parent = 0 ORDER BY name");
    3. while ( $root = mysql_fetch_row($roots_arr) ){
    4.     print "<tr><td>$root[1]</td></tr>\n";
    5.     $prods_arr = mysql_query("SELECT categoryID, productID, name, price, count FROM `SS_products` WHERE majorID = $root[0] AND enabled = 1 ORDER BY name");
    6.     while ( $prods = mysql_fetch_row($prods_arr) ){
    7.         if ( $prods[4] ) $cnt = "+";
    8.         else $cnt = "-";
    9.         print  "<tr>\n";
    10.         print  "<td>$prods[2]</td>\n";
    11.         print  "<td>$prods[3]</td>\n";
    12.         print  "<td>$cnt</td>\n";
    13.         print  "</tr>\n";
    14.     }
    15. }
    16. ?>
    Скорость возросла в 7-8 раз. Чем больше глубина вложений (и рекурсий соответсвенно) тем значительней будет выигрыш.
    Правда, пришлось несколько переделать систему управления расположением категорий и продуктов, но это уже не критично.

    Даже не знаю, стоит ли таскать всё дерево? Делать отдельную таблицу с деревьями (лесопарк)? Блин, таблиц будет, как у собаки блох...


    Ссылка от Xerk очень даже интересная, но это провоцирует на то, чтобы переделывать всю структуру базы и сайта. Я сейчас не готов к такому шагу :)

    В общем, все очень даже неплохо получилось.
    Спасибо всем!
     
  16. topas

    topas Активный пользователь

    С нами с:
    16 авг 2006
    Сообщения:
    2.258
    Симпатии:
    36
    Хулиган
    А как же слова: "Тему можно закрывать?" :)
     
  17. Luge

    Luge Старожил

    С нами с:
    2 фев 2007
    Сообщения:
    4.680
    Симпатии:
    1
    Адрес:
    Минск
    обойдёмся
     
Статус темы:
Закрыта.