Перейти к содержимому


Правила форума

Внимание!!! Если не можете скачать, пожалуйста ознакомьтесь с условиями получения доступа с файлам форума. Правила форума


Ускоряем работу клиентской части ShopCMS при большом количестве товаров и категорий


Сообщений в теме: 41

#21 VerstkaShopcms

    Пользователь

  • Download User
  • PipPip
  • 20 сообщений
Репутация: 4
Начинающий

Отправлено 12 марта 2016 - 14:59

Мной была замечена какая-то околесица условий для запроса выборки товаров для категории, а именно в случае когда есть товары отображаемые в "не родной" категории(в том смысле что в товаре добавлена дополнительная категория) и плюс стоит галочка выводить товары из подкатегорий. Кажется так, надеюсь не перепутал. У самого руки не дошли распутать данный клубок. Знаю что данная фишка не сильно используется народом в шопцмс. Но косяк есть косяк. Извиняюсь если об этом уже писали где-то на форуме.

#22 badisoft

    Продвинутый пользователь

  • VIP
  • 5 006 сообщений
Репутация: 766
Мастер

Отправлено 13 марта 2016 - 21:23

Просмотр сообщенияVerstkaShopcms сказал:

Мной была замечена какая-то околесица условий для запроса выборки товаров для категории
Такой околесицы условий в shopCMS довольно много. Вплоть до безопасного, но бессмысленного "ProductID=$xxx AND ProductID=$xxx". Какие-то моменты я публикую в теме про "штатные ошибки shopCMS из коробки", если есть на это время и желание.

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

Например, в функции prdSearchProductByTemplate для каждого из товаров полученной выборки отдельно получается список характеристик и вариантов:
$row["product_extra"]	 = GetExtraParametrs( $row["productID"] );

хотя функция GetExtraParametrs ШТАТНО позволяет передать в нее массив ProductID и получить на выходе массив характеристик.
Это тоже сократит количество SQL-запросов. Насколько сильно не помню, пока оставил эту модификацию на будущей.
Ну и так далее.
http://cpu.badisoft.ru (тестовый сайт), http://badisoft.ru (модули)

#23 badisoft

    Продвинутый пользователь

  • VIP
  • 5 006 сообщений
Репутация: 766
Мастер

Отправлено 15 марта 2016 - 17:21

Просмотр сообщенияbadisoft сказал:

хотя функция GetExtraParametrs ШТАТНО позволяет передать в нее массив ProductID и получить на выходе массив характеристик. Это тоже сократит количество SQL-запросов. Насколько сильно не помню, пока оставил эту модификацию на будущее.
Я сегодня посмотрел, в чем суть модуля "оптимизация SQL-запросов" от trikiweb-а.
http://vsupport.ru/t...mysql-запросов/
Там эта модификация уже реализована. А также несколько других уменьшающих число SQL-запросов модификаций.
Собственно, вся оптимизация состоит из двух частей:
1. применение библиотеки (класса), которая кэширует SQL-запросы.
2. около пяти-семи модификаций кода, уменьшающих число запросов. В основном путем замены цикла с WHERE field= на один запрос с WHERE field IN.
Пункт (1) несколько глючит (см. тему об этом модуле), надо поправить.
А пункт (2) вполне имеет смысл поставить.
Причем ни одно из исправлений не пересекается с моими вышеперечисленными.
http://cpu.badisoft.ru (тестовый сайт), http://badisoft.ru (модули)

#24 badisoft

    Продвинутый пользователь

  • VIP
  • 5 006 сообщений
Репутация: 766
Мастер

Отправлено 12 апреля 2016 - 08:51

Цитата

Пункт (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-запросу для каждой категории для получения информации о самой категории, а потом по запросу для каждой категории для получения информации о товарах в "дополнительных категориях". В общем, не оптимальненько, но только если категорий - тысячи.
http://cpu.badisoft.ru (тестовый сайт), http://badisoft.ru (модули)

#25 Salp

    Продвинутый пользователь

  • Assistent vsupport.ru
  • PipPipPip
  • 209 сообщений
Репутация: 54
Продвинутый

Отправлено 03 августа 2016 - 16:37

Просмотр сообщенияgolftuning сказал:

# 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 badisoft

    Продвинутый пользователь

  • VIP
  • 5 006 сообщений
Репутация: 766
Мастер

Отправлено 03 августа 2016 - 19:06

Просмотр сообщенияSalp сказал:

Файлы, почему-то не удаляются ( Подозреваю, это из-за того, для ускорения работы был отключен пересчет кол-ва товаров в категориях.
Если отключить пересчет товаров, то не будет выполняться функция update_psCount, куда вносятся эти изменения.
Естественно, если update_psCount никогда не выполняется, то и файлы не удаляются :).
Не надо отключать пересчет, я же выкладывал в теме "Штатные ошибки ShopCMS" переделку штатного алгоритма пересчета, которая работает с нормальной скоростью.
http://cpu.badisoft.ru (тестовый сайт), http://badisoft.ru (модули)

#27 Salp

    Продвинутый пользователь

  • Assistent vsupport.ru
  • PipPipPip
  • 209 сообщений
Репутация: 54
Продвинутый

Отправлено 30 мая 2017 - 14:09

Было бы интересно хотя бы для некоторых блоков (например, тот же список категорий) задействовать кеширование самого смарти, которое, к моему удивлению, не задействовано. Кеширование компилятора - да, работает, а кеширование страниц с исключением лишнего обращения к базе - ниразу!
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 Salp

    Продвинутый пользователь

  • Assistent vsupport.ru
  • PipPipPip
  • 209 сообщений
Репутация: 54
Продвинутый

Отправлено 31 мая 2017 - 12:55

Попробовал закешить главную страницу - она отдается из кеша влет. Время компиляции 0.000с. Обращений к базе в 2р. меньше - остаток работы простейшего ЧПУ (с) badisoft
Без ЧПУ база должна, вообще быть не задействована и кеширование сильно упрощается и может быть более эффективным - мгновенная отдача страницы.
Это особенно актуально для статей, статических страниц и новостей.
Недостатки: кешируется и не обновляется корзина (( Но этот вопрос решаемый - можно исключить из кеша часть шаблона и сформировать отдельно для корзины переменные.
При изменении цен тоже нужно будет делать очистку кеша, но направление работы положено.

#29 badisoft

    Продвинутый пользователь

  • VIP
  • 5 006 сообщений
Репутация: 766
Мастер

Отправлено 31 мая 2017 - 13:15

Цитата

Недостатки: кешируется и не обновляется корзина
Таких "недостатков" будет вагон и тележка, т.к. содержимое большинства страниц динамично и не может быть одинаковым для всех, т.е. из кэша.
Я кроме прайслиста (да и то если валюта одна) и блока категорий (если категорий много) с ходу не вижу других шаблонов, для которых было бы полезно кэширование и они бы не были динамическими. Для статических страниц полезность кэширования мне кажется сомнительной, т.к. шаблон там очень простой и состоит в echo "текст из таблицы". Ну и с header-ом придется что-то делать (например, insert вместо include), т.к. у каждой страницы и блока header динамический в зависимости от "админ/не админ".
http://cpu.badisoft.ru (тестовый сайт), http://badisoft.ru (модули)

#30 Salp

    Продвинутый пользователь

  • Assistent vsupport.ru
  • PipPipPip
  • 209 сообщений
Репутация: 54
Продвинутый

Отправлено 31 мая 2017 - 13:57

Можно как в WP просто не использовать кеширование для аминов или зарег. пользователей.
Кеш особенно важен для поисковиков - гугл, например, ценит быстрые сайты - это важный фактор ранжирования.
Можно даже по юзерагенту определять что это поисковый бот и включать кеширование только тогда - для поисковика сайт будет "летать", как статический.

#31 Salp

    Продвинутый пользователь

  • Assistent vsupport.ru
  • PipPipPip
  • 209 сообщений
Репутация: 54
Продвинутый

Отправлено 12 июня 2017 - 10:25

Подозреваю, что при установленном модуле "Простенький модуль ЧПУ" узким горлышком все-же остается обработка всех этих множества категорий и перевод их в ЧПУ вид в функции "replace_to_cpu".
Может, их сразу сохранять в файловый кеш в чпу виде и выводить так в шаблоне? В результате функция "replace_to_cpu" пробежит по preg_replace_callback, не найдет в категориях совпадений и обращений к базе не будет.

#32 badisoft

    Продвинутый пользователь

  • VIP
  • 5 006 сообщений
Репутация: 766
Мастер

Отправлено 12 июня 2017 - 12:29

Цитата

Подозреваю, что при установленном модуле "Простенький модуль ЧПУ" узким горлышком все-же остается обработка всех этих множества категорий и перевод их в ЧПУ вид в функции "replace_to_cpu".
Да. Каждая ссылка "старого" вида это один запрос к таблице для получения ЧПУ-ссылки.
Если категорий тысяча и вся простыня развернута (например, в случае выпадающего меню с категориями) - будет тысяча запросов.

Цитата

Может, их сразу сохранять в файловый кеш в чпу виде и выводить так в шаблоне?
Да как угодно можно.
Например, в виде готового HTML-кода, если результирующий HTML/CSS неизменен, а выделение текущей категории сделано на JS (тот же Smarty-кэш можно использовать).
Или одним запросом получать весь список категорий, а в функции category_replacer работать уже с этим массивом, а не делая SQL-запрос для каждой категории.
Или вообще массив категорий хранить в serialized-виде файликом на диске. (См "кэшируем в файл клиентский список категорий", но это для обычного списка категорий, без ЧПУ).
Вариантов уменьшения количества SQL-запросов достаточно много, но не всегда уменьшение числа запросов равно увеличению быстродействия. Хранение готового массива категорий (в виде файлика или еще как-то) действительно сильно повышает скорость работы при большом количестве категорий. Даже без ЧПУ. В ЧПУ та же самая проблема просто вылезает еще раз и ее еще раз можно точно так же исправить :).
http://cpu.badisoft.ru (тестовый сайт), http://badisoft.ru (модули)

#33 vakkula

    Продвинутый пользователь

  • Download User
  • PipPipPip
  • 100 сообщений
Репутация: 6
Начинающий

Отправлено 13 июня 2017 - 13:14

Добрый день. А как ускорить живой поиск?

#34 badisoft

    Продвинутый пользователь

  • VIP
  • 5 006 сообщений
Репутация: 766
Мастер

Отправлено 13 июня 2017 - 14:01

Цитата

А как ускорить живой поиск?
Думаю, что никак. В зависимости от версии поиска там один либо два SQL-запроса.
Один совсем простой (если он есть), второй с LEFT JOIN между таблицей товаров и картинок.
Возможно (возможно!) ускорит выполнение второго запроса разбиение его на два - по таблице товаров, а потом путем IN() - по таблице картинок. Но очень сомнительно, что это даст какой-то заметный выигрыш. Если вообще не наоборот.

В общем случае любое "нельзя ли ускорить" надо смотреть и изучать, где же основной затык по времени. И исходя из увиденного уже что-то менять.

PS. Впервые слышу, чтобы в живом поиске надо было что-то ускорять.
http://cpu.badisoft.ru (тестовый сайт), http://badisoft.ru (модули)

#35 Salp

    Продвинутый пользователь

  • Assistent vsupport.ru
  • PipPipPip
  • 209 сообщений
Репутация: 54
Продвинутый

Отправлено 13 июня 2017 - 14:22

Что-то я растерялся - как в вывод function _recursiveGetCategoryCList из category_functions.php добавить поле из таблицы CATEGORIES_TABLE?
В шаблоне все берется из $cats в category_tree.php, а вот как именно формируется...

Прошу помощи.

#36 vakkula

    Продвинутый пользователь

  • Download User
  • PipPipPip
  • 100 сообщений
Репутация: 6
Начинающий

Отправлено 13 июня 2017 - 14:37

Просмотр сообщенияbadisoft (13 июня 2017 - 14:01) писал:

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 badisoft

    Продвинутый пользователь

  • VIP
  • 5 006 сообщений
Репутация: 766
Мастер

Отправлено 13 июня 2017 - 15:44

Цитата

3.03881/select count(*)
Мне кажется, что если select count(*) выполняется 3сек, то ускорять надо не живой поиск, а sql-сервер.
Возможно (хотя и странно), что условие слишком сложное.
Это легко проверить, например, оставив в запросе вместо
LOWER(name) LIKE '%2586453%' OR LOWER(product_code) LIKE '%2586453%' OR LOWER(brief_description) LIKE '%2586453%'
только
LOWER(name) LIKE '%2586453%'
Если скорость выполнения заметно повысится (что, на мой взгляд, врядли), то придется отказаться от поиска по наименованию, коду и описанию, оставив только наименование.

Цитата

Что-то я растерялся - как в вывод function _recursiveGetCategoryCList из category_functions.php добавить поле из таблицы CATEGORIES_TABLE?
Никак :). Эта функция не работает с SQL-таблицей, она путем global использует заранее созданные массивы $fc и $mc.
Они создаются в index.php и в admin.php
Вот в $fc и надо добавить нужное поле из таблицы.

По хорошему функцию _recursiveGetCategoryCList надо просто переписать нахрен, т.к., насколько я вижу, при КАЖДОМ ее вызове идет цикл по ВСЕМУ массиву категорий. А функция эта рекурсивная и сколько раз она сама себя вызовет, столько циклов по ВСЕМУ массиву категорий пройдено и будет.
Т.е. для каждой дочерней категории цикл будет выполнен заново.
А в админке для каждой категории еще и функция catGetCategoryProductCount будет выполнена (а в ней два SQL-запроса), хотя результат этой функции - количество товара в категории - и так уже есть готовый в массиве $fc.
http://cpu.badisoft.ru (тестовый сайт), http://badisoft.ru (модули)

#38 Salp

    Продвинутый пользователь

  • Assistent vsupport.ru
  • PipPipPip
  • 209 сообщений
Репутация: 54
Продвинутый

Отправлено 13 июня 2017 - 16:38

Просмотр сообщенияbadisoft (13 июня 2017 - 15:44) писал:

Никак :). Эта функция не работает с SQL-таблицей, она путем global использует заранее созданные массивы $fc и $mc.
Они создаются в index.php и в admin.php
Вот в $fc и надо добавить нужное поле из таблицы.
Спасибо! Самое интересное, что несколько лет назад я до этого докапывался чтобы добавить поле category_enabled, а сечас снова смотрю за эту функция как "баран на новые ворота" (((

#39 badisoft

    Продвинутый пользователь

  • VIP
  • 5 006 сообщений
Репутация: 766
Мастер

Отправлено 13 июня 2017 - 17:30

Просмотр сообщенияSalp сказал:

а сечас снова смотрю за эту функция как "баран на новые ворота"
Я каждый раз так на нее смотрю. И думаю: "Как надо было обкуриться, чтобы написать эту функцию?"
Неоднократно приходила мысль переписать, но так и не собрался.
http://cpu.badisoft.ru (тестовый сайт), http://badisoft.ru (модули)

#40 badisoft

    Продвинутый пользователь

  • VIP
  • 5 006 сообщений
Репутация: 766
Мастер

Отправлено 14 июня 2017 - 16:40

Просмотр сообщенияbadisoft сказал:

Неоднократно приходила мысль переписать, но так и не собрался
Собрался.
# 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-запросов, что само по себе как цель смысла не имеет.
http://cpu.badisoft.ru (тестовый сайт), http://badisoft.ru (модули)