Производительность и частые ошибки

Проблемы при работе с API инфоблоков усиливаются по мере роста проекта: увеличиваются объем данных и число посещений. Сначала замедляется отдача страниц, затем растет нагрузка на базу данных. В итоге возникают сбои при сохранении данных и дубли в поисковой индексации.

Оптимизировать производительность инфоблоков

При работе с инфоблоками большинство проблем с производительностью возникает из-за избыточных данных: лишние поля, повторяющиеся запросы, неоптимальные фильтры. Начните оптимизацию с контроля над тем, что и как запрашиваете.

Ограничить выборку

Избыточный объем данных увеличивает время выполнения и потребление памяти. Указывайте только те поля, которые действительно нужны.

В компонентах

В компонентах избыточная выборка замедляет отдачу страницы и увеличивает размер кеша.

Например:

  • в $arSelect или select есть поля, которые не используются в шаблоне компонента,

  • выборка включает свойства PROPERTY_* или служебные поля, которые не нужны для отображения.

Выполните проверку полей и внесите исправления.

  1. Откройте папку с шаблоном компонента.

  2. Найдите все обращения к $arResult, особенно в циклах.

    <?= $arResult['ITEMS'][0]['ID'] ?>
            <?= $arResult['ITEMS'][0]['NAME'] ?>
            
  3. Укажите в $arSelect или select поля из $arResult.

    • В классическом API:
    // Получить ID и название без свойств и лишних полей
            CIBlockElement::GetList(
                [],
                ['IBLOCK_ID' => 123],
                false,
                false,
                ['ID', 'NAME']
            );
            
    • В ORM:
    \Bitrix\Iblock\ElementTable::getList([
                'select' => ['ID', 'NAME'],
                'filter' => ['IBLOCK_ID' => 123],
                'limit' => 20,
                'cache' => [
                    'ttl' => 3600,
                    'cache_joins' => true,
                ]
            ]);
            

    Такой кеш эффективен, если данные обновляются не чаще одного раза в час. При частом изменении элементов кеширование отключают.

Вне компонентов

Пример агента, который раз в час обновляет статистику по активным элементам.

function updateActiveElementsStat()
        {
            $cache = new \CPHPCache();
            if ($cache->InitCache(3600, 'active_elements_count', '/stats/'))
            {
                $count = $cache->GetVars()['count'];
            } else {
                if ($cache->StartDataCache())
                {
                    // Выбираем только ID и дату начала активности
                    $iterator = \Bitrix\Iblock\ElementTable::getList([
                        'select' => ['ID', 'DATE_ACTIVE_FROM'],
                        'filter' => [
                            'IBLOCK_ID' => 123,
                            'ACTIVE' => 'Y',
                            '>=ACTIVE_FROM' => ConvertTimeStamp(time() - 86400, 'FULL')
                        ],
                        'limit' => 1000, // Защита от переполнения памяти
                    ]);
        
                    $count = 0;
                    while ($element = $iterator->fetch())
                    {
                        $count++;
                    }
        
                    // Регистрируем тег для автоматического сброса при изменении инфоблока
                    $taggedCache = \Bitrix\Main\Application::getInstance()->getTaggedCache();
                    $taggedCache->startTagCache('/stats/');
                    $taggedCache->registerTag('iblock_id_123');
                    $taggedCache->endTagCache();
        
                    $cache->EndDataCache(['count' => $count]);
                }
            }
        
            return true;
        }
        

При изменении данных в инфоблоке вызовите сброс кеша:

$taggedCache = \Bitrix\Main\Application::getInstance()->getTaggedCache();
        $taggedCache->clearByTag('iblock_id_123');
        

Выбрать подходящий метод получения данных

Метод GetNext() приводит данные к безопасному виду и обрабатывает шаблоны ссылок на элемент, раздел, инфоблок.

Метод Fetch() работает быстрее, чем GetNext(), но данные нужно обработать вручную.

Пример безопасного использования Fetch():

$row = $result->Fetch();
        if ($row)
        {
            $row['NAME'] = \Bitrix\Main\Text\HtmlFilter::encode($row['NAME']);
        }
        

Контролировать состав результата кеширования

По умолчанию компонент кеширует весь массив $arResult. Если $arResult содержит много полей или вложенных данных, размер кеш-файла растет, а скорость чтения падает.

Проверьте размер кеша.

  1. В папке /bitrix/cache/ найдите папку кеша для компонента, например, news.list.

  2. Откройте самый свежий файл.

  3. Посмотрите его размер. Если файл больше 1 МБ — кеш избыточен.

Сократите размер кеша за три шага.

  1. В файле component.php объявите только нужные поля:

    $this->SetResultCacheKeys(['ID', 'NAME', 'DATE_ACTIVE_FROM']);
            
  2. Убедитесь, что в $arResult нет лишних полей.

  3. Обновите страницу и проверьте размер нового файла кеша.

Если в файле result_modifier.php формируют данные для component_epilog.php, их можно включить в кеш:

  • поместите данные в $arResult, например, $arResult['IDS'] = [...]),

  • добавьте ключ в SetResultCacheKeys() в component.php:

    $this->SetResultCacheKeys(['IDS']);
            

Использовать nTopCount вместо nPageSize

В методе CIBlockElement::GetList() четвертый параметр — это настройки навигации. Они позволяют ограничить выборку двумя способами:

  • nPageSize — задает количество элементов на странице при постраничной навигации,

    // Выполняет два запроса: COUNT (сколько всего) + SELECT (данные)
            CIBlockElement::GetList([], ['IBLOCK_ID' => 123], false, ['nPageSize' => 10], ['ID', 'NAME']);
            
  • nTopCount — ограничивает общее число записей в результате запроса.

    // Выполняет один запрос: SELECT … LIMIT 10
            CIBlockElement::GetList([], ['IBLOCK_ID' => 123], false, ['nTopCount' => 10], ['ID', 'NAME']);
            

    Если инфоблок хранит свойства в общей таблице, один элемент с множественными свойствами порождает несколько строк. Например, чтобы получить два элемента, у которых множественное свойство содержит два и три значения, установите nTopCount = 5.

Используйте nTopCount в следующих ситуациях:

  • на странице нет постраничной навигации,

  • не нужно знать общее количество элементов,

  • на странице выводят фиксированное число элементов.

Разница в скорости заметна при большом количестве элементов в инфоблоке.

Избегать LIKE в фильтрах

Метод getList() без оператора = превращает фильтр в LIKE, например, 'CODE' => 'news'. Это замедляет запрос.

Чтобы отфильтровать записи по точному значению, укажите оператор =.

// Классический API
        CIBlockElement::GetList([], [
            '=CODE' => 'news'
        ]);
        
        // ORM
        \Bitrix\Iblock\ElementTable::getList([
            'filter' => ['=CODE' => 'news']
        ]);
        
        // query() автоматически использует %=
        \Bitrix\Iblock\ElementTable::query()
            ->where('CODE', 'news')
            ->exec();
        

Проанализировать SQL-запросы

  1. Откройте страницу Настройки > Производительность > SQL-запросы.

  2. Найдите запросы внутри циклов. Это видно по повторяющемуся SQL-коду и большому количеству записей с одинаковым значением в столбце Компонент.

    Чтобы исправить, соберите все идентификаторы в массив и сделайте один запрос с фильтром ['ID' => $ids].

  3. Проверьте использование индексов.

    • Выберите подозрительный запрос и откройте План исполнения.

    • Если в строке key указано <null>, а в условиях есть Using filesort или Using temporary, запрос не использует индекс.

    • Добавьте составной индекс по полям, которые часто фильтруют вместе: IBLOCK_ID, ACTIVE, SORT или IBLOCK_ID, CODE.

  4. Найдите лишние подсчеты.

    • Ищите запросы вида SELECT COUNT(*) FROM b_iblock_element. Если на странице нет постраничной навигации, такой запрос избыточен.

    • Замените nPageSize на nTopCount в параметрах навигации.

Вынести настройки выборки в параметры компонента

Когда один и тот же компонент используют на разных страницах, объем данных часто различается. Например:

  • на главной — только ID и NAME для слайдера,

  • в каталоге — NAME, PREVIEW_TEXT, DETAIL_PAGE_URL.

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

Чтобы избежать этого, вынесите список запрашиваемых полей в параметры компонента .parameters.php. Тогда состав выборки можно настроить в визуальном редакторе без изменения кода.

'PARAMETERS' => [
            'SELECT_FIELDS' => [
                'PARENT' => 'DATA_SOURCE',
                'NAME' => 'Выбираемые поля',
                'TYPE' => 'LIST',
                'MULTIPLE' => 'Y',
                'VALUES' => [
                    'ID' => 'ID',
                    'NAME' => 'Название',
                    'CODE' => 'Символьный код',
                    'DATE_ACTIVE_FROM' => 'Дата начала активности'
                ],
                'DEFAULT' => ['ID', 'NAME']
            ]
        ]
        

В файле component.php используйте параметр SELECT_FIELDS:

$arSelect = $arParams['SELECT_FIELDS'] ?: ['ID', 'NAME'];
        $res = CIBlockElement::GetList([], $filter, false, false, $arSelect);
        

Выполните настройку параметров компонента. Компонент будет запрашивать только те поля, которые действительно используются. Это сокращает объем передаваемых данных и ускоряет отдачу.

Частые ошибки при работе с инфоблоками

Ошибки при работе с инфоблоками имеют четкие признаки и стандартные решения.

Не подключен модуль

Если возникает ошибка Fatal error: Class 'ElementTable' not found или Fatal error: Class 'CIBlockElement' not found, модуль iblock не подключен.

Добавьте в начало скрипта:

\Bitrix\Main\Loader::includeModule('iblock');
        

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

Не найден ORM-класс

Если возникает ошибка вида Class '\Bitrix\Iblock\Elements\ElementNewsTable' not found, у инфоблока не указан cимвольный код API.

Укажите API_CODE для инфоблока в административном разделе, сбросьте кеш и выполните компиляцию.

Не сохраняются значения свойств

Если свойства типа Список или Привязка к элементу не сохраняются, проверьте формат передаваемых значений и настройки инфоблока.

Свойство типа Список

Передайте ID значения из таблицы b_iblock_property_enum. Идентификатор можно получить методом CIBlockPropertyEnum::GetList().

Пример корректного сохранения элемента для инфоблока с 'API_CODE' => 'News':

$element = \Bitrix\Iblock\Elements\ElementNewsTable::createObject()
            ->setName('Новость')
            ->set('SOURCE', 105); // 105 — ID варианта в b_iblock_property_enum
        
        $element->save();
        

Свойство типа Привязка к элементу

Первый способ. Передайте ID целевого элемента — универсальный способ.

$element = \Bitrix\Iblock\Elements\ElementNewsTable::createObject()
            ->setName('Обзор товара')
            // RELATED_ITEM — код свойства типа Привязка к элементу
            ->set('RELATED_ITEM', 789); // 789 — ID элемента в инфоблоке Товары
        $element->save();
        

Второй способ. Передайте XML_ID целевого элемента, если у целевого инфоблока задан API_CODE и скомпилирован ORM-класс. Например, \Bitrix\Iblock\IblockTable::compileEntity('Products').

$element = \Bitrix\Iblock\Elements\ElementNewsTable::createObject()
            ->setName('Обзор')
            ->set('RELATED_ITEM', 'product-2026'); // XML_ID из инфоблока Товары
        
        $element->save();
        

Проверьте:

  • у свойства типа Привязка к элементу указан инфоблок привязки LINK_IBLOCK_ID,

  • целевой элемент существует и активен,

  • для множественных свойств используйте addTo('CODE', значение), а не set().

Переменная не является объектом

Если возникает ошибка Call to a member function GetNextElement() on a non-object, нарушен порядок или тип параметров в вызове CIBlockElement::GetList().

Используйте правильную сигнатуру:

CIBlockElement::GetList(
            [],           // сортировка
            $arFilter,    // фильтр
            false,        // группировка: только true/false
            false,        // параметры навигации: массив или false
            $arSelect     // выборка полей
        );
        

Страница возвращает неверный код

Если несуществующая страница элемента возвращает код состояния 200 OK, поисковые системы ее индексируют.

Чтобы исправить проблему, в шаблоне компонента до вывода HTML установите код 404:

if (!$arResult['ELEMENT'])
        {
            \CHTTP::SetStatus('404 Not Found');
            require $_SERVER['DOCUMENT_ROOT'].'/404.php';
            exit;
        }