Речь пойдет об оптимизации выборки элементов со свойствами из подразделов выбранного раздела - задача весьма стандартная. Возможно, это баян (хотя ссылок на подобное не нашел) или уже неактуально в новых версиях, но буду рад, если кому-то будет полезным.
[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 в последнем приведенном коде).