Оптимізація запитів до інфоблоків 1С-Бітрікс
Інфоблок Бітрікс — гнучкий, але важкий інструмент. Таблиці b_iblock_element, b_iblock_element_property, b_iblock_section, b_iblock_element_property_enum утворюють реляційну структуру, яка при необережних запитах породжує жахливі плани виконання. EXPLAIN запиту до списку 100 товарів з 5 властивостями нерідко показує перебір мільйонів рядків і кілька секунд виконання. Для каталогів з активним трафіком це прямо впливає на TTFB та навантаження на MySQL.
Проблеми старого API (CIBlockElement::GetList)
Старий API Бітрікс (CIBlockElement::GetList) при запиті з фільтром PROPERTY_ генерує запит з JOIN до b_iblock_element_property для кожної властивості. При 5 властивостях у фільтрі — 5 JOIN-ів. При цьому API не дозволяє явно керувати планом запиту.
Додаткова проблема — параметр arSelect. Якщо передати "*" або пропустити явний список полів, API запитує всі поля, включаючи об'ємні текстові описи та непотрібні метадані. Для сторінки каталогу з 48 товарами це множить обсяг переданих даних з БД у 3–5 разів.
Перехід на D7 API (Iblock\ElementTable)
API D7 (\Bitrix\Iblock\ElementTable) надає Query Builder з явним контролем select, filter, order, limit. Кожен запит транслюється в SQL і може бути проаналізований через getQuery()->getSql() до виконання.
Приклад оптимального запиту для сторінки каталогу:
use Bitrix\Iblock\ElementTable;
$result = ElementTable::getList([
'select' => [
'ID',
'NAME',
'CODE',
'PREVIEW_PICTURE',
'IBLOCK_SECTION_ID',
],
'filter' => [
'=IBLOCK_ID' => CATALOG_IBLOCK_ID,
'=ACTIVE' => 'Y',
'=IBLOCK_SECTION_ID' => $sectionId,
],
'order' => ['SORT' => 'ASC', 'ID' => 'ASC'],
'limit' => 24,
'offset' => $page * 24,
'cache' => ['ttl' => 3600],
]);
Явний select без властивостей — запит лише до b_iblock_element, без JOIN до b_iblock_element_property. Якщо властивості потрібні для частини товарів (наприклад, лише для тих, що потрапили на сторінку), вони запитуються окремим запитом за списком ID — це патерн «запит-по-ID-батчами» замість N+1.
N+1 — головний ворог продуктивності інфоблоку
Проблема N+1 у контексті Бітрікс: отримали список із 24 елементів, а потім для кожного елемента в циклі викликаєте CIBlockElement::GetProperty() або окремий GetList для пов'язаних даних. Підсумок: 1 запит списку + 24 запити властивостей + 24 запити пов'язаних елементів = 49 запитів.
Рішення — пакетна вибірка:
// Отримуємо ID всіх елементів
$ids = array_column($elements, 'ID');
// Один запит для всіх властивостей
$propsResult = \CIBlockElement::GetPropertyValuesArray(
$ids,
CATALOG_IBLOCK_ID,
['CODE' => ['BRAND', 'COLOR', 'SIZE']]
);
Або через D7 з використанням \Bitrix\Iblock\ElementPropertyTable для вибірки властивостей списком ID.
Кешування на рівні компонентів vs на рівні даних
Кеш компонента (файловий, через initCache) — грубий інструмент: кешується весь HTML-фрагмент, інвалідується за тегом IBLOCK_N. Для сторінок з персоналізацією або частими оновленнями каталогу це незручно.
Кешування на рівні даних через ManagedCache з гранулярними тегами (по розділу, по набору властивостей) дозволяє інвалідувати лише зачеплені записи. Витрати — необхідність явно прописувати кеш у кожному місці звернення до даних.
Кейс: каталог будматеріалів, 45 000 SKU
До оптимізації: сторінка розділу каталогу (48 товарів) — 1,8 с TTFB (без кешу), 340 мс з кешем. MySQL slow query log показував: один запит до списку товарів — 640 мс, другий запит «схожих товарів» — 310 мс, сумарно 12 запитів на сторінку.
Виявлені проблеми:
-
CIBlockElement::GetListзSELECT => "*"іPROPERTY_FILTER => [5 властивостей] - N+1 у блоці «схожі товари»: запит до кожного з 6 товарів окремо
- Запит розділу з повним деревом дочірніх розділів без обмеження рівня
Що зробили:
- Переписали основний запит на D7, explicit select (8 полів замість усіх)
- Пакетний запит властивостей для блоку «схожі товари»
- Обмеження дерева розділів через параметр
DEPTH_LEVELі кеш з тегом розділу - Додавання індексів на
b_iblock_element: складений по(IBLOCK_ID, IBLOCK_SECTION_ID, ACTIVE, SORT)
Результат: сторінка розділу (без кешу) — 380 мс TTFB, з кешем — 45 мс. Кількість запитів до БД: 12 → 4. Навантаження на MySQL знизилося, з'явилася можливість обслуговувати в 3 рази більше одночасних користувачів без додавання потужностей.
Складені індекси для інфоблоків
Стандартні індекси Бітрікс не покривають усі типові вибірки. Критичний індекс для каталогу:
ALTER TABLE b_iblock_element
ADD INDEX idx_iblock_section_active_sort
(IBLOCK_ID, IBLOCK_SECTION_ID, ACTIVE, SORT);
Аналіз поточних індексів через SHOW INDEX FROM b_iblock_element і EXPLAIN конкретних запитів зі slow log — обов'язковий крок перед додаванням.
Терміни
| Етап | Термін |
|---|---|
| Аудит запитів (slow log, Explain, панель Бітрікс) | 1–2 дні |
| Переробка критичних запитів (D7, batching) | 3–7 днів |
| Індекси та налаштування кешу | 1–2 дні |
На великих проєктах з великою кількістю кастомних компонентів — до 3 тижнів.







