Ускоряем работу клиентской части ShopCMS при большом количестве товаров и категорий
#21
Отправлено 12 March 2016 - 02:59 PM
#22
Отправлено 13 March 2016 - 09:23 PM
Такой околесицы условий в shopCMS довольно много. Вплоть до безопасного, но бессмысленного "ProductID=$xxx AND ProductID=$xxx". Какие-то моменты я публикую в теме про "штатные ошибки shopCMS из коробки", если есть на это время и желание.Мной была замечена какая-то околесица условий для запроса выборки товаров для категории
Ну а функции выборки (товаров, ордеров) в силу попытки, как мне кажется, сделать универсальные функции выборки, а затем наслоения туда все новых и новых условий, вариантов и исключений в конечном итоге получились довольно монстрообразны и медленны.
Например, в функции prdSearchProductByTemplate для каждого из товаров полученной выборки отдельно получается список характеристик и вариантов:
$row["product_extra"] = GetExtraParametrs( $row["productID"] );
хотя функция GetExtraParametrs ШТАТНО позволяет передать в нее массив ProductID и получить на выходе массив характеристик.
Это тоже сократит количество SQL-запросов. Насколько сильно не помню, пока оставил эту модификацию на будущей.
Ну и так далее.
#23
Отправлено 15 March 2016 - 05:21 PM
Я сегодня посмотрел, в чем суть модуля "оптимизация SQL-запросов" от trikiweb-а.хотя функция GetExtraParametrs ШТАТНО позволяет передать в нее массив ProductID и получить на выходе массив характеристик. Это тоже сократит количество SQL-запросов. Насколько сильно не помню, пока оставил эту модификацию на будущее.
http://vsupport.club...mysql-запросов/
Там эта модификация уже реализована. А также несколько других уменьшающих число SQL-запросов модификаций.
Собственно, вся оптимизация состоит из двух частей:
1. применение библиотеки (класса), которая кэширует SQL-запросы.
2. около пяти-семи модификаций кода, уменьшающих число запросов. В основном путем замены цикла с WHERE field= на один запрос с WHERE field IN.
Пункт (1) несколько глючит (см. тему об этом модуле), надо поправить.
А пункт (2) вполне имеет смысл поставить.
Причем ни одно из исправлений не пересекается с моими вышеперечисленными.
#24
Отправлено 12 April 2016 - 08:51 AM
Пункт (1) несколько глючит (см. тему об этом модуле), надо поправить.
Чтобы не приходилось дважды пересохранять "Общие настройки" надо:
1. в файле conf_setting.php после
$smarty->assign("controls", settingCallHtmlFunctions((int)$_GET["settings_groupID"]) );
вставляем
# BEGIN оптимизация SQL-запросов
if ( isset($_POST["save"]) ) Redirect(ADMIN_FILE."?dpt=conf&sub=setting&settings_groupID=".(int)$_GET["settings_groupID"]);
# END оптимизация SQL-запросов
Ну и еще пара полезностей по части оптимизации SQL-запросов:
======================================================
Убираем рекурсию, создающую кучу SQL-запросов при большом количестве вложенных категорий за счет получения ровно тех же данных (список categoryID всех подкатегорий заданной категории) из уже готового массива $cats, где эти данные лежат в массиве подряд.
На одном из сайтов с большим количеством категорий (1713) это сэкономило более тысячи SQL-запросов на странице категории.
function catGetSubCategories( $categoryID )
{
# BEGIN оптимизируем SQL-запросы
/*
$q = db_query("select categoryID from ".CATEGORIES_TABLE." where parent=".(int)$categoryID);
$r = array();
while ($row = db_fetch_row($q))
{
$a = catGetSubCategories($row[0]);
$c_a = count($a);
for ($i=0;$i<$c_a;$i++) $r[] = $a[$i];
$r[] = $row[0];
}
return $r;
*/
global $cats;
$sub = array();
foreach ($cats as $key => $val)
{
if ($val['categoryID'] == $categoryID)
{
$i = $key;
$last = count($cats)-1;
while (++$i <= $last && $cats[$i]['level']>$val['level']) $sub[] = $cats[$i]['categoryID'];
break;
}
}
return $sub;
# END оптимизируем SQL-запросы
}
=======================================
Убираем вызовы функции _getConditionWithCategoryConj (один вызов - один SQL-запрос) для каждой из подкатегорий, когда установлена галка "Показывать товары из подкатегорий".
На одном из сайтов с большим количеством категорий (1713) это сэкономило более тысячи SQL-запросов на странице категории.
для версии 3.1 (и ниже?):
function _getConditionWithCategoryConjWithSubCategories( $condition, $categoryID ) //fetch products from current category and all its subcategories
{
$new_condition = "";
$categoryID_Array = catGetSubCategories( $categoryID );
$categoryID_Array[] = (int)$categoryID;
# BEGIN оптимизируем SQL-запросы
/*
foreach( $categoryID_Array as $catID )
{
if ( $new_condition != "" )
$new_condition .= " OR ";
$new_condition .= _getConditionWithCategoryConj($condition, $catID);
}
*/
$incat = implode(',',$categoryID_Array);
$data = db_query("SELECT productID FROM ".CATEGORIY_PRODUCT_TABLE." WHERE categoryID IN($incat)");
$inprd = array();
while ($row = db_fetch_assoc($data)) $inprd[] = $row['productID'];
$new_condition = (count($inprd)?('productID IN('.implode(',',$inprd).') OR '):'')."categoryID IN($incat)";
if ($condition != "") $new_condition = "$condition AND ($new_condition)";
# END оптимизируем SQL-запросы
return $new_condition;
}
для версии 3.1.2 и выше (чуть отличается исходная функция):
function _getConditionWithCategoryConjWithSubCategories( $condition, $categoryID ) //fetch products from current category and all its subcategories
{
$new_condition = "";
$tempcond = "";
$categoryID_Array = catGetSubCategories( $categoryID );
$categoryID_Array[] = (int)$categoryID;
# BEGIN оптимизируем SQL-запросы
/*
foreach( $categoryID_Array as $catID )
{
if ( $new_condition != "" )
$new_condition .= " OR ";
$new_condition .= _getConditionWithCategoryConj($tempcond, $catID);
}
*/
$incat = implode(',',$categoryID_Array);
$data = db_query("SELECT productID FROM ".CATEGORIY_PRODUCT_TABLE." WHERE categoryID IN($incat)");
$inprd = array();
while ($row = db_fetch_assoc($data)) $inprd[] = $row['productID'];
$new_condition = (count($inprd)?('productID IN('.implode(',',$inprd).') OR '):'')."categoryID IN($incat)";
# if ($condition != "") $new_condition = "$condition AND ($new_condition)";
# END оптимизируем SQL-запросы
if ( $condition == "" ) return $new_condition;
else return $condition." AND (".$new_condition.")";
}
Было:
Работа с БД: 1.520 сек
Запросов в БД: 2181
Стало:
Работа с БД: 0.091 сек
Запросов в БД: 150
Еще раз подчеркиваю:
Если у Вас двадцать-сто-двести категорий, то это дополнение практически ничего не изменит. Т.е. изменит, конечно, но на совершенно несущественные величины. Оно актуально тогда, когда категорий тысячи. Конкретно в описываемом случае категория первого уровня имела более тысячи дочерних категорий и при штатном коде сначала было по одному SQL-запросу для каждой категории для получения информации о самой категории, а потом по запросу для каждой категории для получения информации о товарах в "дополнительных категориях". В общем, не оптимальненько, но только если категорий - тысячи.
#25
Отправлено 03 August 2016 - 04:37 PM
Файлы, почему-то не удаляются (# BEGIN кэшируем в файл клиентский список категорий
if (file_exists("core/temp/catcache.txt")) unlink("core/temp/catcache.txt");
if (file_exists("core/temp/categories.txt")) unlink("core/temp/categories.txt");
# END кэшируем в файл клиентский список категорий
Подозреваю, это из-за того, для ускорения работы был отключен пересчет кол-ва товаров в категориях.
#26
Отправлено 03 August 2016 - 07:06 PM
Если отключить пересчет товаров, то не будет выполняться функция update_psCount, куда вносятся эти изменения.Файлы, почему-то не удаляются ( Подозреваю, это из-за того, для ускорения работы был отключен пересчет кол-ва товаров в категориях.
Естественно, если update_psCount никогда не выполняется, то и файлы не удаляются .
Не надо отключать пересчет, я же выкладывал в теме "Штатные ошибки ShopCMS" переделку штатного алгоритма пересчета, которая работает с нормальной скоростью.
#27
Отправлено 30 May 2017 - 02:09 PM
require('Smarty.class.php');
$smarty = new Smarty;
$smarty->caching = true;
$my_cache_id = $_GET['article_id'];
if(!$smarty->is_cached('index.tpl',$my_cache_id)) {
// Кэша нет, поэтому присваиваем значение переменным.
$contents = get_database_contents();
$smarty->assign($contents);
}
$smarty->display('index.tpl',$my_cache_id);
С этим подходом статические страницы можно выхватывать из кеша полностью минуя обращение к базе, присваивание переменных и компиляцию шаблона.
Но это уже высший пилотаж ))
Пример из Prestashop
#28
Отправлено 31 May 2017 - 12:55 PM
Без ЧПУ база должна, вообще быть не задействована и кеширование сильно упрощается и может быть более эффективным - мгновенная отдача страницы.
Это особенно актуально для статей, статических страниц и новостей.
Недостатки: кешируется и не обновляется корзина (( Но этот вопрос решаемый - можно исключить из кеша часть шаблона и сформировать отдельно для корзины переменные.
При изменении цен тоже нужно будет делать очистку кеша, но направление работы положено.
#29
Отправлено 31 May 2017 - 01:15 PM
Таких "недостатков" будет вагон и тележка, т.к. содержимое большинства страниц динамично и не может быть одинаковым для всех, т.е. из кэша.Недостатки: кешируется и не обновляется корзина
Я кроме прайслиста (да и то если валюта одна) и блока категорий (если категорий много) с ходу не вижу других шаблонов, для которых было бы полезно кэширование и они бы не были динамическими. Для статических страниц полезность кэширования мне кажется сомнительной, т.к. шаблон там очень простой и состоит в echo "текст из таблицы". Ну и с header-ом придется что-то делать (например, insert вместо include), т.к. у каждой страницы и блока header динамический в зависимости от "админ/не админ".
#30
Отправлено 31 May 2017 - 01:57 PM
Кеш особенно важен для поисковиков - гугл, например, ценит быстрые сайты - это важный фактор ранжирования.
Можно даже по юзерагенту определять что это поисковый бот и включать кеширование только тогда - для поисковика сайт будет "летать", как статический.
#31
Отправлено 12 June 2017 - 10:25 AM
Может, их сразу сохранять в файловый кеш в чпу виде и выводить так в шаблоне? В результате функция "replace_to_cpu" пробежит по preg_replace_callback, не найдет в категориях совпадений и обращений к базе не будет.
#32
Отправлено 12 June 2017 - 12:29 PM
Да. Каждая ссылка "старого" вида это один запрос к таблице для получения ЧПУ-ссылки.Подозреваю, что при установленном модуле "Простенький модуль ЧПУ" узким горлышком все-же остается обработка всех этих множества категорий и перевод их в ЧПУ вид в функции "replace_to_cpu".
Если категорий тысяча и вся простыня развернута (например, в случае выпадающего меню с категориями) - будет тысяча запросов.
Да как угодно можно.Может, их сразу сохранять в файловый кеш в чпу виде и выводить так в шаблоне?
Например, в виде готового HTML-кода, если результирующий HTML/CSS неизменен, а выделение текущей категории сделано на JS (тот же Smarty-кэш можно использовать).
Или одним запросом получать весь список категорий, а в функции category_replacer работать уже с этим массивом, а не делая SQL-запрос для каждой категории.
Или вообще массив категорий хранить в serialized-виде файликом на диске. (См "кэшируем в файл клиентский список категорий", но это для обычного списка категорий, без ЧПУ).
Вариантов уменьшения количества SQL-запросов достаточно много, но не всегда уменьшение числа запросов равно увеличению быстродействия. Хранение готового массива категорий (в виде файлика или еще как-то) действительно сильно повышает скорость работы при большом количестве категорий. Даже без ЧПУ. В ЧПУ та же самая проблема просто вылезает еще раз и ее еще раз можно точно так же исправить .
#33
Отправлено 13 June 2017 - 01:14 PM
#34
Отправлено 13 June 2017 - 02:01 PM
Думаю, что никак. В зависимости от версии поиска там один либо два SQL-запроса.А как ускорить живой поиск?
Один совсем простой (если он есть), второй с LEFT JOIN между таблицей товаров и картинок.
Возможно (возможно!) ускорит выполнение второго запроса разбиение его на два - по таблице товаров, а потом путем IN() - по таблице картинок. Но очень сомнительно, что это даст какой-то заметный выигрыш. Если вообще не наоборот.
В общем случае любое "нельзя ли ускорить" надо смотреть и изучать, где же основной затык по времени. И исходя из увиденного уже что-то менять.
PS. Впервые слышу, чтобы в живом поиске надо было что-то ускорять.
#35
Отправлено 13 June 2017 - 02:22 PM
В шаблоне все берется из $cats в category_tree.php, а вот как именно формируется...
Прошу помощи.
#36
Отправлено 13 June 2017 - 02:37 PM
PS. Впервые слышу, чтобы в живом поиске надо было что-то ускорять.
Очень много товара и 2 запроса выполняются в среднем по 3 сек.
3.03881/select count(*) from aaa_products where categoryID>1 and enabled=1 and ( LOWER(name) LIKE '%2586453%' OR LOWER(product_code) LIKE '%2586453%' OR LOWER(brief_description) LIKE '%2586453%' ) AND in_stock > 0
3.3954/select categoryID, name, brief_description, customers_rating, Price, in_stock, customer_votes, list_price, productID, default_picture, sort_order, items_sold, enabled, product_code, description, shipping_freight, viewed_times, min_order_amount from aaa_products where categoryID>1 and enabled=1 and ( LOWER(name) LIKE '%2586453%' OR LOWER(product_code) LIKE '%2586453%' OR LOWER(brief_description) LIKE '%2586453%' ) AND in_stock > 0 order by date_added DESC LIMIT 0,30
#37
Отправлено 13 June 2017 - 03:44 PM
Мне кажется, что если select count(*) выполняется 3сек, то ускорять надо не живой поиск, а sql-сервер.3.03881/select count(*)
Возможно (хотя и странно), что условие слишком сложное.
Это легко проверить, например, оставив в запросе вместо
LOWER(name) LIKE '%2586453%' OR LOWER(product_code) LIKE '%2586453%' OR LOWER(brief_description) LIKE '%2586453%'только
LOWER(name) LIKE '%2586453%'Если скорость выполнения заметно повысится (что, на мой взгляд, врядли), то придется отказаться от поиска по наименованию, коду и описанию, оставив только наименование.
Никак . Эта функция не работает с SQL-таблицей, она путем global использует заранее созданные массивы $fc и $mc.Что-то я растерялся - как в вывод function _recursiveGetCategoryCList из category_functions.php добавить поле из таблицы CATEGORIES_TABLE?
Они создаются в index.php и в admin.php
Вот в $fc и надо добавить нужное поле из таблицы.
По хорошему функцию _recursiveGetCategoryCList надо просто переписать нахрен, т.к., насколько я вижу, при КАЖДОМ ее вызове идет цикл по ВСЕМУ массиву категорий. А функция эта рекурсивная и сколько раз она сама себя вызовет, столько циклов по ВСЕМУ массиву категорий пройдено и будет.
Т.е. для каждой дочерней категории цикл будет выполнен заново.
А в админке для каждой категории еще и функция catGetCategoryProductCount будет выполнена (а в ней два SQL-запроса), хотя результат этой функции - количество товара в категории - и так уже есть готовый в массиве $fc.
#38
Отправлено 13 June 2017 - 04:38 PM
Спасибо! Самое интересное, что несколько лет назад я до этого докапывался чтобы добавить поле category_enabled, а сечас снова смотрю за эту функция как "баран на новые ворота" (((Никак . Эта функция не работает с SQL-таблицей, она путем global использует заранее созданные массивы $fc и $mc.
Они создаются в index.php и в admin.php
Вот в $fc и надо добавить нужное поле из таблицы.
#39
Отправлено 13 June 2017 - 05:30 PM
Я каждый раз так на нее смотрю. И думаю: "Как надо было обкуриться, чтобы написать эту функцию?"а сечас снова смотрю за эту функция как "баран на новые ворота"
Неоднократно приходила мысль переписать, но так и не собрался.
#40
Отправлено 14 June 2017 - 04:40 PM
Собрался.Неоднократно приходила мысль переписать, но так и не собрался
# BEGIN упростим функцию _recursiveGetCategoryCList
/*
функция вызывается только из catGetCategoryCList() и catGetCategoryCListMin()
$parent - categoryID родительской категории, от которого получают список дочерних. Обычно равно 0 (Главная категория)
$level - по логике это level категории $parent (т.е. обычно 0), но по идее может быть любым, при рекурсии он +1.
$expcat - массив из categoryID, для которых надо в свою очередь построить дерево дочерних категорий (вызвать эту же func рекурсивно)
(если не массив, а null, то для всех).
$_indexType - 'NUM' - категории располагаются в массиве c последовательным индексом (0,1,2,3,4..), если 'ASSOC', то индекс элемента массива равен categoryID
$cprod - считать только enabled=1 товары (true) или все (false)
$ccat - считать количество товаров в категории (true)
*/
global $fc, $mc;
$rcats = array_keys($mc, (int)$parent);
$result = array(); //parents
foreach ($rcats as $rcat)
{
$row = $fc[(int)$rcat];
$row['level'] = $level;
$row['ExistSubCategories'] = ($row['subcount'] != 0);
$row['ExpandedCategory'] = ($expcat == null || in_array((int)$row['categoryID'],$expcat));
if (!file_exists('data/category/'.$row['picture'])) $row['picture'] = '';
# Новый вариант расчета products_count_category (без SQL-запросов, используя $fc)
$in_childs = 0;
$count_name = $cprod?'products_count':'products_count_admin';
foreach (array_keys($mc, $row['categoryID']) as $childID) $in_childs += $fc[$childID][$count_name];
if ($ccat) $row['products_count_category'] = $row[$count_name] - $in_childs;
# старый вариант (с SQL-запросами)
# if ($ccat) $row['products_count_category'] = catGetCategoryProductCount( $row['categoryID'], $cprod );
if($_indexType=='NUM') $result[] = $row;
elseif ($_indexType=='ASSOC') $result[$row['categoryID']] = $row;
if ($row['ExpandedCategory'] && $row['ExistSubCategories'])
{
$subcategories = _recursiveGetCategoryCList( $row['categoryID'],$level+1, $expcat, $_indexType, $cprod, $ccat);
foreach ($subcategories as $_sub)
{
if($_indexType=='NUM') $result[] = $_sub;
elseif ($_indexType=='ASSOC') $result[$_sub['categoryID']] = $_sub;
}
}
}
return $result;
# END упростим функцию _recursiveGetCategoryCList
Алгоритм отставлен тот же самый, он оказался по ближайшем рассмотрении вполне нормальный.
Просто причесан вид и сглажены некоторые шероховатости.
1. упрощен расчет $row['ExpandedCategory'].
2. изменен расчет $row['products_count_category'].
3. ну и совсем уж декоративные мелочи.
Не думаю, что даст какое-то заметное увеличение быстродействия.
На базе с ~1000 категорий при разворачивании в админке полного списка категорий я визуально разницы не заметил.
Только уменьшение количества SQL-запросов, что само по себе как цель смысла не имеет.