Поиск продуктов по атрибутам в WebAsyst ShopScript
16 февраля 2013 в 17:01 в категории Статьи о php 2 комментарияДобрый день, уважаемые читатели блога!
Давно не писал в блог в связи с отсутствием интересного и необычного. Но! Вчера мир перевернулся! И не потому что на Землю летят метеориты и т.п.
Дело в том, что сейчас мной дорабатывается движок компании WebAsyst ShopScript, а именно улучшается работа поиска по атрибутам.
По умолчанию, поиск по атрибутам предусматривает выбор одного значения каждого из атрибутов, а в улучшенной версии поиска можно выбирать несколько значений атрибута, с помощью checkbox’ов.
В чем же проблема у движка ShopScript?
Итак, открыв файл C:\WebServers\home\domain\www\published\SC\html\scripts\core_functions\product_functions.php
Я внес все необходимые изменения чтобы появилась возможность выбирать несколько атрибутов и все это нормально обрабатывалось.
После пары моих тестов на локальной машине все работало отлично, но страница достаточно долго грузилась, и я решил посмотреть логи БД MSQL, где увидел запросы следующего типа:
SELECT SQL_CALC_FOUND_ROWS p.*, name_ru AS _name_sort FROM SC_products p LEFT JOIN SC_product_options_values PrdOptVal0 ON p.productID=PrdOptVal0.`productID` LEFT JOIN SC_product_options_set PrdOptSet0 ON p.productID=PrdOptSet0.`productID` LEFT JOIN SC_product_options_values PrdOptVal1 ON p.productID=PrdOptVal1.`productID` LEFT JOIN SC_product_options_set PrdOptSet1 ON p.productID=PrdOptSet1.`productID` LEFT JOIN SC_product_options_values PrdOptVal2 ON p.productID=PrdOptVal2.`productID` LEFT JOIN SC_product_options_set PrdOptSet2 ON p.productID=PrdOptSet2.`productID` LEFT JOIN SC_product_options_values PrdOptVal3 ON p.productID=PrdOptVal3.`productID` LEFT JOIN SC_product_options_set PrdOptSet3 ON p.productID=PrdOptSet3.`productID` LEFT JOIN SC_product_options_values PrdOptVal4 ON p.productID=PrdOptVal4.`productID` LEFT JOIN SC_product_options_set PrdOptSet4 ON p.productID=PrdOptSet4.`productID` LEFT JOIN SC_product_options_values PrdOptVal5 ON p.productID=PrdOptVal5.`productID` LEFT JOIN SC_product_options_set PrdOptSet5 ON p.productID=PrdOptSet5.`productID` LEFT JOIN SC_product_options_values PrdOptVal6 ON p.productID=PrdOptVal6.`productID` LEFT JOIN SC_product_options_set PrdOptSet6 ON p.productID=PrdOptSet6.`productID` LEFT JOIN SC_product_options_values PrdOptVal7 ON p.productID=PrdOptVal7.`productID` LEFT JOIN SC_product_options_set PrdOptSet7 ON p.productID=PrdOptSet7.`productID` LEFT JOIN SC_product_options_values PrdOptVal8 ON p.productID=PrdOptVal8.`productID` LEFT JOIN SC_product_options_set PrdOptSet8 ON p.productID=PrdOptSet8.`productID` LEFT JOIN SC_product_options_values PrdOptVal9 ON p.productID=PrdOptVal9.`productID` LEFT JOIN SC_product_options_set PrdOptSet9 ON p.productID=PrdOptSet9.`productID` LEFT JOIN SC_product_options_values PrdOptVal10 ON p.productID=PrdOptVal10.`productID` LEFT JOIN SC_product_options_set PrdOptSet10 ON p.productID=PrdOptSet10.`productID` LEFT JOIN SC_product_options_values PrdOptVal11 ON p.productID=PrdOptVal11.`productID` LEFT JOIN SC_product_options_set PrdOptSet11 ON p.productID=PrdOptSet11.`productID` LEFT JOIN SC_product_options_values PrdOptVal12 ON p.productID=PrdOptVal12.`productID` LEFT JOIN SC_product_options_set PrdOptSet12 ON p.productID=PrdOptSet12.`productID` LEFT JOIN SC_product_options_values PrdOptVal13 ON p.productID=PrdOptVal13.`productID` LEFT JOIN SC_product_options_set PrdOptSet13 ON p.productID=PrdOptSet13.`productID` LEFT JOIN SC_product_options_values PrdOptVal14 ON p.productID=PrdOptVal14.`productID` LEFT JOIN SC_product_options_set PrdOptSet14 ON p.productID=PrdOptSet14.`productID` LEFT JOIN SC_product_options_values PrdOptVal15 ON p.productID=PrdOptVal15.`productID` LEFT JOIN SC_product_options_set PrdOptSet15 ON p.productID=PrdOptSet15.`productID` LEFT JOIN SC_product_options_values PrdOptVal16 ON p.productID=PrdOptVal16.`productID` LEFT JOIN SC_product_options_set PrdOptSet16 ON p.productID=PrdOptSet16.`productID` LEFT JOIN SC_product_options_values PrdOptVal17 ON p.productID=PrdOptVal17.`productID` LEFT JOIN SC_product_options_set PrdOptSet17 ON p.productID=PrdOptSet17.`productID` LEFT JOIN SC_product_options_values PrdOptVal18 ON p.productID=PrdOptVal18.`productID` LEFT JOIN SC_product_options_set PrdOptSet18 ON p.productID=PrdOptSet18.`productID` LEFT JOIN SC_product_options_values PrdOptVal19 ON p.productID=PrdOptVal19.`productID` LEFT JOIN SC_product_options_set PrdOptSet19 ON p.productID=PrdOptSet19.`productID` WHERE ((categoryID IN (558,559,560,561,562,563,564,565,566,567,568,569,590,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,591,592,593,594,595,557)) AND ( enabled=1)) AND ( PrdOptVal0.optionID=17 AND ( ( PrdOptVal0.option_type=1 AND PrdOptSet0.variantID=18) OR (PrdOptVal0.option_type=0 AND PrdOptVal0.option_value_ru LIKE '%Смартфон%') )) OR ( PrdOptVal0.optionID=17 AND ( ( PrdOptVal0.option_type=1 AND PrdOptSet0.variantID=17) OR (PrdOptVal0.option_type=0 AND PrdOptVal0.option_value_ru LIKE '%Телефон%') )) AND ( PrdOptVal1.optionID=19 AND ( ( PrdOptVal1.option_type=1 AND PrdOptSet1.variantID=19) OR (PrdOptVal1.option_type=0 AND PrdOptVal1.option_value_ru LIKE '%Android 2.2%') )) OR ( PrdOptVal1.optionID=19 AND ( ( PrdOptVal1.option_type=1 AND PrdOptSet1.variantID=21) OR (PrdOptVal1.option_type=0 AND PrdOptVal1.option_value_ru LIKE '%Android 2.3%') )) AND ( PrdOptVal2.optionID=20 AND ( ( PrdOptVal2.option_type=1 AND PrdOptSet2.variantID=31) OR (PrdOptVal2.option_type=0 AND PrdOptVal2.option_value_ru LIKE '%1 слот%') )) AND ( PrdOptVal3.optionID=23 AND ( ( PrdOptVal3.option_type=1 AND PrdOptSet3.variantID=71) OR (PrdOptVal3.option_type=0 AND PrdOptVal3.option_value_ru LIKE '%MTK%') )) AND ( PrdOptVal4.optionID=24 AND ( ( PrdOptVal4.option_type=1 AND PrdOptSet4.variantID=103) OR (PrdOptVal4.option_type=0 AND PrdOptVal4.option_value_ru LIKE '%1.0 GHz%') )) AND ( PrdOptVal5.optionID=25 AND ( ( PrdOptVal5.option_type=1 AND PrdOptSet5.variantID=83) OR (PrdOptVal5.option_type=0 AND PrdOptVal5.option_value_ru LIKE '%Mali 300%') )) AND ( PrdOptVal6.optionID=26 AND ( ( PrdOptVal6.option_type=1 AND PrdOptSet6.variantID=87) OR (PrdOptVal6.option_type=0 AND PrdOptVal6.option_value_ru LIKE '%1 Гб%') )) AND ( PrdOptVal7.optionID=27 AND ( ( PrdOptVal7.option_type=1 AND PrdOptSet7.variantID=60) OR (PrdOptVal7.option_type=0 AND PrdOptVal7.option_value_ru LIKE '%1 Гб%') )) AND ( PrdOptVal8.optionID=28 AND ( ( PrdOptVal8.option_type=1 AND PrdOptSet8.variantID=65) OR (PrdOptVal8.option_type=0 AND PrdOptVal8.option_value_ru LIKE '%до 1 Гб%') )) AND ( PrdOptVal9.optionID=29 AND ( ( PrdOptVal9.option_type=1 AND PrdOptSet9.variantID=100) OR (PrdOptVal9.option_type=0 AND PrdOptVal9.option_value_ru LIKE '%aGPS%') )) AND ( PrdOptVal10.optionID=31 AND ( ( PrdOptVal10.option_type=1 AND PrdOptSet10.variantID=34) OR (PrdOptVal10.option_type=0 AND PrdOptVal10.option_value_ru LIKE '%есть%') )) AND ( PrdOptVal11.optionID=32 AND ( ( PrdOptVal11.option_type=1 AND PrdOptSet11.variantID=116) OR (PrdOptVal11.option_type=0 AND PrdOptVal11.option_value_ru LIKE '%ёмкостной%') )) AND ( PrdOptVal12.optionID=36 AND ( ( PrdOptVal12.option_type=1 AND PrdOptSet12.variantID=91) OR (PrdOptVal12.option_type=0 AND PrdOptVal12.option_value_ru LIKE '%0,3 Мп%') )) AND ( PrdOptVal13.optionID=38 AND ( ( PrdOptVal13.option_type=1 AND PrdOptSet13.variantID=39) OR (PrdOptVal13.option_type=0 AND PrdOptVal13.option_value_ru LIKE '%есть%') )) AND ( PrdOptVal14.optionID=39 AND ( ( PrdOptVal14.option_type=1 AND PrdOptSet14.variantID=16) OR (PrdOptVal14.option_type=0 AND PrdOptVal14.option_value_ru LIKE '%есть%') )) AND ( PrdOptVal15.optionID=40 AND ( ( PrdOptVal15.option_type=1 AND PrdOptSet15.variantID=36) OR (PrdOptVal15.option_type=0 AND PrdOptVal15.option_value_ru LIKE '%есть%') )) AND ( PrdOptVal16.optionID=41 AND ( ( PrdOptVal16.option_type=1 AND PrdOptSet16.variantID=11) OR (PrdOptVal16.option_type=0 AND PrdOptVal16.option_value_ru LIKE '%есть%') )) AND ( PrdOptVal17.optionID=42 AND ( ( PrdOptVal17.option_type=1 AND PrdOptSet17.variantID=29) OR (PrdOptVal17.option_type=0 AND PrdOptVal17.option_value_ru LIKE '%есть%') )) AND ( PrdOptVal18.optionID=43 AND ( ( PrdOptVal18.option_type=1 AND PrdOptSet18.variantID=13) OR (PrdOptVal18.option_type=0 AND PrdOptVal18.option_value_ru LIKE '%есть%') )) AND ( PrdOptVal19.optionID=15 AND ( ( PrdOptVal19.option_type=1 AND PrdOptSet19.variantID=5) OR (PrdOptVal19.option_type=0 AND PrdOptVal19.option_value_ru LIKE '%белый%') )) GROUP BY p.productID ORDER BY sort_order, in_stock DESC, _name_sort LIMIT 0,15
38 LEFT JOIN’oв! (для понимания — 2 джоина = 0,02 сек, 4 джоина = 8,8 сек.)
По два на каждый атрибут! Т.е. если у вас у товара хотя бы 5 атрибутов — готовьтесь к 10 JOIN’aм, которые положат вашу БД! 😉
Я обратил внимание на 543 и 512 строчки вышеупомянутого файла. Там расположено тело функции _prepareSearchExtraParameters, которая собственно и отвечает за поиск продуктов.
Что же такого в этих строчках? Объясню.
Не знаю, для чего нужен был этот костыль, и возможно я не прав, за что заранее приношу свои извенения разработчикам, но в теле цикла, начинающего на 507 строке начинает генерироваться для каждого атрибута 2хJOIN LEFT, и при всем при этом присоединяется одна и та же таблица просто с каждым разом ее имя PrdOptVal инкрементируется (PrdOptVal1, PrdOptVal2). Как мы все знаем, JOIN’ы не самые легкие запросы, и для чего присоединять одну и ту же таблицу много раз?
Таким образом я пришел к выводу, что не все то хороший движок, что стоит 7950 руб!
Вот так, дорогие друзья! Удачного кодинга и оптимизированного кода вам! 😉
Продолжение тут — WebAsyst наплевать на владельцев ShopScript или как положить БД владельца интернет-магазина ShopScript
2 комментария
join’ов много для комбинирования фиксированных строковых параметров и выбираемых, если параметров не больше 1 на каждую опцию то запрос довольно быстро проходит, а если же параметров больше то время запроса растёт по экспоненциальной. По идее там надо 4 join’а 2 на фиксированные и 2 на выбираемые параметры, я сделал так — убрал перекрёстные сравнения строковых параметров с выбираемыми и использовал straight join плюс отсортировал так чтобы сначала джоинились бы опции у которых меньше параметров. Ну и плюс ограничение на 61 join — итого доступно всего 30 параметров для выбора 🙂 А убрать $cnt у меня не получилось просто ничего не находил 🙂 Щас запрос размером 50кб идёт за 2 сек — многовато но там свои нюансы 🙂
Дело в том что присоединяются одни и те же таблицы с одними и теми же параметрами. Смысла 0. Я думал что это некий необходимый костыль, но так и не нашел ему применения. Если вы можете предложить вариант где оправдывается такое поведение запроса — благодарность )