Перейти к содержанию

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

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

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

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

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

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

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

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

Например:

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

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

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

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

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

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

  2. В классическом 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']);
  1. Убедитесь, что в $arResult нет лишних полей.

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

Если в файле 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].

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

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

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

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

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

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

  7. Замените 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;
}