Речь пойдет об оптимизации выборки элементов со свойствами из подразделов выбранного раздела - задача весьма стандартная. Возможно, это баян (хотя ссылок на подобное не нашел) или уже неактуально в новых версиях, но буду рад, если кому-то будет полезным.
[spoiler]
Рассмотрим такую задачу:
Нужно получить список всех элементов из подразделов заданного раздела с их свойствами (допустим, все товары из подкатегорий большой категории с характеристиками). При этом, допустим, свойства хранятся в общей таблице. База - MySQL.
Самый простой способ решить такую задачу - следующий:
В результате получаем все элементы из подразделов со всеми свойствами - точнее 458 элементов.
При этом отладчик показывает, что было выполнено 465!!! запросов и суммарное время на исполнение запросов 12.5113 сек (на локальной не сильно оптимизированной под Битрикс машине).
Разберемся более детально с запросами:
Есть 1 основной крокодильного вида запрос:
который исполняется около 10 секунд, а также куча мелких быстрых запросов вида:
с минимальным временем исполнения.
В первом запросе есть такая часть
- таким образом, производится выборка подразделов при включенном INCLUDE_SUBSECTIONS. Тут фактически присутствует двойное сканирование таблицы b_iblock_section для определения подкатегорий.
Перепишем код следующим образом:
Таким образом, мы избавились от указанного двойного сканирования и сократили общее времы исполнения запросов до 2 секунд (в 5 раз).
Теперь попробуем разобраться с кучей схожих мелких указанных запросов. Тут дело в том, что каждый вызов функции
генерирует запрос на получение свойств элементы из таблицы b_iblock_property и зависимых от нее таблиц. То есть для каждого элемента - дополнительный запрос, что совсем не круто при условии непрогнозируемости кол-ва элементов.
Выход тут может быть следующим - определить необходимые свойства и запрашивать их вместе с основным запросом:
При этом общее кол-во запросов на странице - 11, общее время исполнения запросов менее 0.1 секунды, что уже вполне приемлемо.
Таким образом, утратив некоторую универсальность (теперь необходимо вручную создавать массив $Element["PROPERTIES"] ) мы получили profit в производительности и прогнозируемость кода.
Напоследок, отмечу еще одно еще одно классическое, но часто забываемое правило - не проси у базы больше, чем тебе нужно (если вам нужно узнать, например, только название и ID элементов - укажите эти поля вместо звездочки в последнем аргументе функции CIBlockElement::GetList в последнем приведенном коде).
[spoiler]
Рассмотрим такую задачу:
Нужно получить список всех элементов из подразделов заданного раздела с их свойствами (допустим, все товары из подкатегорий большой категории с характеристиками). При этом, допустим, свойства хранятся в общей таблице. База - MySQL.
Самый простой способ решить такую задачу - следующий:
$rsResult = CIBlockElement::GetList( array( "SORT"=>"ASC" ), array( "ACTIVE" => "Y", "IBLOCK_ID" => $IBLOCK_ID, "SECTION_CODE" => $SECTION_CODE, "INCLUDE_SUBSECTIONS" => "Y" ), false, false, array() ); while( $obElement = $rsResult->GetNextElement() ) { $Element = $obElement->GetFields(); $Element["PROPERTIES"] = $obElement->GetProperties(); echo "<pre>"; print_r( $Element ); echo "</pre>"; } // End while |
При этом отладчик показывает, что было выполнено 465!!! запросов и суммарное время на исполнение запросов 12.5113 сек (на локальной не сильно оптимизированной под Битрикс машине).
Разберемся более детально с запросами:
Есть 1 основной крокодильного вида запрос:
SEL ECT DISTINCT BE.SORT as SORT,BE.ID as ID,DATE_FORMAT(BE.TIMESTAMP_X, '%d.%m.%Y %H:%i:%s') as TIMESTAMP_X,BE.MODIFIED_BY as MODIFIED_BY,DATE_FORMAT(BE.DATE_CREATE, '%d.%m.%Y %H:%i:%s') as DATE_CREATE,BE.CREATED_BY as CREATED_BY,BE.IBLOCK_ID as IBLOCK_ID,BE.IBLOCK_SECTION_ID as IBLOCK_SECTION_ID,BE.ACTIVE as ACTIVE,IF(EXTRACT(HOUR_SECOND FR OM BE.ACTIVE_FR OM)>0, DATE_FORMAT(BE.ACTIVE_FROM, '%d.%m.%Y %H:%i:%s'), DATE_FORMAT(BE.ACTIVE_FROM, '%d.%m.%Y')) as ACTIVE_FROM,IF(EXTRACT(HOUR_SECOND FR OM BE.ACTIVE_TO)>0, DATE_FORMAT(BE.ACTIVE_TO, '%d.%m.%Y %H:%i:%s'), DATE_FORMAT(BE.ACTIVE_TO, '%d.%m.%Y')) as ACTIVE_TO,IF(EXTRACT(HOUR_SECOND FR OM BE.ACTIVE_FROM)>0, DATE_FORMAT(BE.ACTIVE_FROM, '%d.%m.%Y %H:%i:%s'), DATE_FORMAT(BE.ACTIVE_FROM, '%d.%m.%Y')) as DATE_ACTIVE_FROM,IF(EXTRACT(HOUR_SECOND FROM BE.ACTIVE_TO)>0, DATE_FORMAT(BE.ACTIVE_TO, '%d.%m.%Y %H:%i:%s'), DATE_FORMAT(BE.ACTIVE_TO, '%d.%m.%Y')) as DATE_ACTIVE_TO,BE.NAME as NAME,BE.PREVIEW_PICTURE as PREVIEW_PICTURE,BE.PREVIEW_TEXT as PREVIEW_TEXT,BE.PREVIEW_TEXT_TYPE as PREVIEW_TEXT_TYPE,BE.DETAIL_PICTURE as DETAIL_PICTURE,BE.DETAIL_TEXT as DETAIL_TEXT,BE.DETAIL_TEXT_TYPE as DETAIL_TEXT_TYPE,BE.SEARCHABLE_CONTENT as SEARCHABLE_CONTENT,BE.WF_STATUS_ID as WF_STATUS_ID,BE.WF_PARENT_ELEMENT_ID as WF_PARENT_ELEMENT_ID,BE.WF_NEW as WF_NEW,if (BE.WF_DATE_LOCK is null, 'green', if(DATE_ADD(BE.WF_DATE_LOCK, interval 60 MINUTE)<now(), 'green', if(BE.WF_LOCKED_BY=42, 'yellow', 'red'))) as LOCK_STATUS,BE.WF_LOCKED_BY as WF_LOCKED_BY,DATE_FORMAT(BE.WF_DATE_LOCK, '%d.%m.%Y %H:%i:%s') as WF_DATE_LOCK,BE.WF_COMMENTS as WF_COMMENTS,BE.IN_SECTIONS as IN_SECTIONS,BE.SHOW_COUNTER as SHOW_COUNTER,BE.SHOW_COUNTER_START as SHOW_COUNTER_START,BE.CODE as CODE,BE.TAGS as TAGS,BE.XML_ID as XML_ID,BE.XML_ID as EXTERNAL_ID,BE.TMP_ID as TMP_ID,concat('(',U.LOGIN,') ',ifnull(U.NAME,''),' ',ifnull(U.LAST_NAME,'')) as USER_NAME,concat('(',UL.LOGIN,') ',ifnull(UL.NAME,''),' ',ifnull(UL.LAST_NAME,'')) as LOCKED_USER_NAME,concat('(',UC.LOGIN,') ',ifnull(UC.NAME,''),' ',ifnull(UC.LAST_NAME,'')) as CREATED_USER_NAME,L.DIR as LANG_DIR,B.LID as LID,B.IBLOCK_TYPE_ID as IBLOCK_TYPE_ID,B.CODE as IBLOCK_CODE,B.NAME as IBLOCK_NAME,B.XML_ID as IBLOCK_EXTERNAL_ID,B.DETAIL_PAGE_URL as DETAIL_PAGE_URL,B.LIST_PAGE_URL as LIST_PAGE_URL,DATE_FORMAT(BE.DATE_CREATE, '%Y.%m.%d') as CREATED_DATE,if(BE.WF_STATUS_ID = 1, 'Y', 'N') as BP_PUBLISHED FROM b_iblock B INNER JOIN b_lang L ON B.LID=L.LID INNER JOIN b_iblock_element BE ON BE.IBLOCK_ID = B.ID INNER JOIN ( SEL ECT DISTINCT BSE.IBLOCK_ELEMENT_ID FROM b_iblock_section_element BSE INNER JOIN b_iblock_section BSubS ON BSE.IBLOCK_SECTION_ID = BSubS.ID INNER JOIN b_iblock_section BS ON (BSubS.IBLOCK_ID=BS.IBLOCK_ID AND BSubS.LEFT_MARGIN>=BS.LEFT_MARGIN AND BSubS.RIGHT_MARGIN<=BS.RIGHT_MARGIN) WH ERE ((BS.CODE IN ('<код>'))) ) BES ON BES.IBLOCK_ELEMENT_ID = BE.ID LEFT JOIN b_user U ON U.ID=BE.MODIFIED_BY LEFT JOIN b_user UL ON UL.ID=BE.WF_LOCKED_BY LEFT JOIN b_user UC ON UC.ID=BE.CREATED_BY WH ERE 1=1 AND ( ((((BE.ACTIVE='Y')))) AND ((((BE.IBLOCK_ID = '<число>')))) ) AND (((BE.WF_STATUS_ID=1 AND BE.WF_PARENT_ELEMENT_ID IS NULL))) ORDER BY BE.SORT asc |
SELECT BP.*, BEP.ID as PROPERTY_VALUE_ID, BEP.VALUE, BEP.DESCRIPTION, BEPE.VALUE VALUE_ENUM, BEPE.XML_ID VALUE_XML_ID FR OM b_iblock B INNER JOIN b_iblock_property BP ON B.ID=BP.IBLOCK_ID LEFT JOIN b_iblock_element_property BEP ON (BP.ID = BEP.IBLOCK_PROPERTY_ID AND BEP.IBLOCK_ELEMENT_ID = 439) LEFT JOIN b_iblock_property_enum BEPE ON (BP.PROPERTY_TYPE = 'L' AND BEPE.ID=BEP.VALUE_ENUM AND BEPE.PROPERTY_ID=BP.ID) WH ERE B.ID = 13 AND BP.ACTIVE='Y' ORDER BY BP.SORT asc, BP.ID asc, BEPE.SORT asc, BEP.ID asc |
В первом запросе есть такая часть
... INNER JOIN ( SELECT DISTINCT BSE.IBLOCK_ELEMENT_ID FR OM b_iblock_section_element BSE INNER JOIN b_iblock_section BSubS ON BSE.IBLOCK_SECTION_ID = BSubS.ID INNER JOIN b_iblock_section BS ON (BSubS.IBLOCK_ID=BS.IBLOCK_ID AND BSubS.LEFT_MARGIN>=BS.LEFT_MARGIN AND BSubS.RIGHT_MARGIN<=BS.RIGHT_MARGIN) WH ERE ... |
Перепишем код следующим образом:
$rsSectBorders = CIBlockSection::GetList( array( "SORT"=>"ASC" ), array( "ACTIVE" => "Y", "IBLOCK_ID" => $IBLOCK_ID, "CODE" => $SECTION_CODE, ), false ); $SectBorders = $rsSectBorders->GetNext(); $rsSections = CIBlockSection::GetList( array( "SORT"=>"ASC" ), array( "ACTIVE" => "Y", "IBLOCK_ID" => $IBLOCK_ID, ">LEFT_MARGIN" => $SectBorders["LEFT_MARGIN"], "<RIGHT_MARGIN" => $SectBorders["RIGHT_MARGIN"], ), false ); $SectionIDSArray = array(); while( $Section = $rsSections->GetNext() ) { $SectionIDSArray[] = $Section["ID"]; } // End while $rsResult = CIBlockElement::GetList( array( "SORT"=>"ASC" ), array( "ACTIVE" => "Y", "IBLOCK_ID" => $IBLOCK_ID, "SECTION_ID" => $SectionIDSArray, "INCLUDE_SUBSECTIONS" => "N" ), false, false, array() ); while( $obElement = $rsResult->GetNextElement() ) { $Element = $obElement->GetFields(); $Element["PROPERTIES"] = $obElement->GetProperties(); echo "<pre>"; print_r( $Element ); echo "</pre>"; } // End while |
Теперь попробуем разобраться с кучей схожих мелких указанных запросов. Тут дело в том, что каждый вызов функции
$obElement->GetProperties(); |
Выход тут может быть следующим - определить необходимые свойства и запрашивать их вместе с основным запросом:
$rsResult = CIBlockElement::GetList( array( "SORT"=>"ASC" ), array( "ACTIVE" => "Y", "IBLOCK_ID" => $IBLOCK_ID, "SECTION_ID" => $SectionIDSArray, "INCLUDE_SUBSECTIONS" => "N" ), false, false, array( "*", "PROPERTY_PROP_1", "PROPERTY_PROP_2" ) ); while( $Element = $rsResult->GetNext() ) { echo "<pre>"; print_r( $Element ); echo "</pre>"; } // End while |
Таким образом, утратив некоторую универсальность (теперь необходимо вручную создавать массив $Element["PROPERTIES"] ) мы получили profit в производительности и прогнозируемость кода.
Напоследок, отмечу еще одно еще одно классическое, но часто забываемое правило - не проси у базы больше, чем тебе нужно (если вам нужно узнать, например, только название и ID элементов - укажите эти поля вместо звездочки в последнем аргументе функции CIBlockElement::GetList в последнем приведенном коде).