Как ускорить выборку в 1с Битрикс в 20 раз

от автора

Задача — сформировать фид для выгрузки.

Дано:

  • кол-во товаров более 50 000

  • Товары выгружаются с пользовательскими свойствами

Результат эксперимента

ускорение в 40 раз
ускорение в 40 раз

Довольно частая задача — это формирование фидов, что подразумевает под собой выгрузку большого кол-ва элементов.

Битрикс часто ругают за то — что при простых операциях — происходит огромное ко-во запросов к БД, я видел на проектах , как для отображения простой страницы каталога, битрикс делал более 5000 запросов к БД. Именно из- за этого проекты на Битрикс иногда очень требовательны к железу на хостинге.

Современное ядро D7 позволяет значительно снизить нагрузку на БД и сильно ускоряет все процессы связанные с обращением к БД.

Долгое время на одном проекте работал скрипт по формированию фида для загрузки на сторонние сервисы. Скрипт работал, никаких сбоев не выдавал, и все всех устраивало, но вот пришло время вносить изменения и в него.

Первое что бросилось в глаза это то. как в этом скрипте происходила выборка товаров из каталога и добавление к ним пользовательских свойств

\Bitrix\Main\Diag\Debug::startTimeLabel('f_time1'); $obj_items=\CIBlockElement::GetList([] ,$filter, false, false, $select); while ($objItem = $obj_items->GetNextElement()) {     $arItem=$objItem->GetFields();     $result[$arItem['ID']] = $arItem;     $result[$arItem['ID']]['PROPERTIES'] = $objItem->GetProperties();    } \Bitrix\Main\Diag\Debug::endTimeLabel('f_time1'); $f_time=\Bitrix\Main\Diag\Debug::getTimeLabels(); $str_res= "Кол-во эл-в выборки Старое ядро: ".$obj_items->SelectedRowsCount().PHP_EOL; $str_res.= "Время выполонения выборки Старое ядро: ".round($f_time['f_time1']['time'], 8, PHP_ROUND_HALF_UP).PHP_EOL; echo $str_res;
Это конечно никуда не годится
Это конечно никуда не годится

Первое что пришло на ум это изменить добавление свойств к элементу

 $obj_items=\CIBlockElement::GetList([] ,$filter, false, false, $select); while ($arItem = $obj_items->fetch()) {     $result[$arItem['ID']] = $arItem;     $result[$arItem['ID']]['PROPERTIES'] = [];     $ids[] = $arItem['ID']; } $chunks = array_chunk($ids, 1000); foreach ($chunks as $key => $chunk) {     \CIBlockElement::GetPropertyValuesArray(         $result,         2,         ['ID' => $chunk],         ['CODE' => $properties],         ['GET_RAW_DATA' => 'Y']     ); } \Bitrix\Main\Diag\Debug::endTimeLabel('f_time1'); $f_time=\Bitrix\Main\Diag\Debug::getTimeLabels(); $str_res= "Кол-во эл-в выборки Старое ядро: ".$obj_items->SelectedRowsCount().PHP_EOL; $str_res.= "Время выполонения выборки Старое ядро: ".round($f_time['f_time1']['time'], 8, PHP_ROUND_HALF_UP).PHP_EOL; echo $str_res;

Что дало уже приемлемый результат:

Уже улучшил время выборки в 30 раз
Уже улучшил время выборки в 30 раз

Но, обращаться к БД в цикле — это точно нехорошо.

Поэтому решил попробовать Переписать этот код с использованием возможностей ядра D7

 //список свойств //CODE свойств изменены $properties = [     'PROP_1',     'PROP_2',     'PROP_3',     'PROP_4',     'PROP_5',     'PROP_6',     'PROP_7',     'PROP_8',     'PROP_9',     'PROP_10',     'PROP_11',     'PROP_12',     'PROP_13',     'PROP_14',     'PROP_15',     'PROP_16',     'PROP_17',     'PROP_18',     'PROP_19',     'PROP_20',     'PROP_21',     'PROP_22',     'PROP_23',     'PROP_24',     'PROP_25', ]; //ищем IBLOCK_ID по CODE $CatalogiblockId = Helper::getIblockIdByCode("catalog");  foreach ( $properties as  $property_code) {     //ID свойства по CODE     $PROP_ARTICLE_ID = Helper::getIblockPropIDByCode($property_code, $CatalogiblockId);      $props['PROPERTY_'.$PROP_ARTICLE_ID ]= ['data_type' => 'string'];  } $props['IBLOCK_ELEMENT_ID']= ['data_type' => 'integer'];   $entityProps = Bitrix\Main\Entity\Base::compileEntity(     'PROPS',     $props,     [         'table_name' => sprintf('b_iblock_element_prop_s%s', $CatalogiblockId),     ] ); $select = [     'ID',     'IBLOCK_ID',     'NAME',     'SORT',     'IBLOCK_SECTION_ID',     'DETAIL_PICTURE',     'PROPS' ];  $result = \Bitrix\Iblock\ElementTable::getList([     'select'  => $select,     'filter'  => [         'IBLOCK_ID' => $CatalogiblockId,     ],          'runtime' => [         'PROPS'         => [             'data_type' => $entityProps->getDataClass(),             'reference' => [                 '=this.ID' => 'ref.IBLOCK_ELEMENT_ID',             ],         ],     ], ]); \Bitrix\Main\Diag\Debug::endTimeLabel('f_time1');  $f_time=\Bitrix\Main\Diag\Debug::getTimeLabels();  echo "Кол-во эл-в выборки D7: ".$result->getSelectedRowsCount().PHP_EOL; echo "Время выполонения выборки D7: ".round($f_time['f_time1']['time'], 4, PHP_ROUND_HALF_UP).PHP_EOL;

И результат который очень порадовал

прирост производительности 80 раз
прирост производительности 80 раз

Т.к. получить свойства элементов напрямую нельзя, то сначала создаем сущность $entityProps которая содержит поля из таблицы b_iblock_element_prop_s.IBLOCK_ID где наименование столбцов свойств являются PROPERTY_.ID (ID свойства, поэтому надо их сначала получить по CODE, или добавить посмотрев в свойствах элементов)

Затем мы делаем выборку присоединив сущность к выборке.

Данный пример показывает не только как ускорить выполнение выборки на больших массивах, но и может помочь существенно снизить нагрузку на БД даже в обычных каталогах. Где на первый взгляд время выборки не столь существенно. Ведь на выборке в 30 товаров из каталога разница во времени будет не существенной, и на скорость загрузки это будет влиять минимально.

Но если взять во внимание существенное снижение количества запросов к БД — это может существенно снизить нагрузку на БД. И проведя простую оптимизацию — вы добьетесь потрясающих результатов.

Итог:

  • Добились ускорения выполнения скрипта с 1333 сек до 0,552, т.е. почти в 700 раз.

  • Снижение количества запросов к БД с более 50 000. до ОДНОГО.


ссылка на оригинал статьи https://habr.com/ru/post/664950/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *