Оптимізація JOIN-запитів у 1С-Бітриксі
Бітрикс активно використовує JOIN у своєму SQL: запит до CIBlockElement::GetList() з виборкою властивостей може об'єднувати 8–12 таблиць. На малих обсягах це непомітно. Коли в b_iblock_element 500 000 рядків, а в b_iblock_element_property — 10 мільйонів, неоптимальний JOIN перетворюється в багатосекундну операцію.
Як Бітрикс будує JOIN-запити
Коли ви передаєте властивості в $arSelectFields або фільтруєте за ними, Бітрикс додає JOIN до кількох таблиць залежно від типу зберігання властивості:
-
Звичайні властивості —
LEFT JOIN b_iblock_element_property AS p1 ON p1.IBLOCK_ELEMENT_ID = be.ID AND p1.IBLOCK_PROPERTY_ID = N -
Множественні властивості — кожне значення окремим рядком у
b_iblock_element_property, JOIN повертає кілька рядків на елемент -
UTS-властивості (користувацькі типи) — окремена таблиця
b_uts_iblock_N_singleабоb_uts_iblock_N_multiple, JOIN заIBLOCK_ELEMENT_ID -
Властивість-список (L) — додатковий JOIN до
b_iblock_property_enumдля отримання значень
Проблема: якщо запросити 10 властивостей для 1000 елементів, Бітрикс будує запит з 10+ JOIN. MySQL виконує вкладені цикли — для кожного рядка з однієї таблиці проходить по пов'язаній. Без індексів за ключами JOIN це full scan на кожній ітерації.
Головні причини повільних JOIN у Бітриксі
1. Відсутність індексів на колонках з'єднання.
Найчастіший випадок — JOIN за IBLOCK_ELEMENT_ID у b_iblock_element_property без індекса. Бітрикс створює індекс ix1 за (IBLOCK_ELEMENT_ID) при установці, але з зростанням обсягу даних цього може бути недостатньо. EXPLAIN показує type=ALL за таблицею властивостей.
Перевірте:
SHOW INDEX FROM b_iblock_element_property;
SHOW INDEX FROM b_uts_iblock_5_single; -- для інфоблока ID 5
Для UTS-таблиць індекси не створюються автоматично при додаванні властивостей через адміністративний інтерфейс.
2. JOIN за строковим полем VALUE при числових даних.
Поле VALUE у b_iblock_element_property має тип TEXT або VARCHAR(255). Фільтрація WHERE p.VALUE = '100' — це порівняння рядків, індекс за TEXT-полем неефективний, приведення типів при JOIN ламає використання індекса. Для властивостей з числовими значеннями Бітрикс дублює дані у полі VALUE_NUM (FLOAT) — використовуйте його.
3. Запит множественних властивостей в одному JOIN.
Виборка 5 множественних властивостей дублює рядки: якщо у елемента 3 значення властивості A та 4 значення властивості B — запит повертає 12 рядків на елемент. MySQL обробляє декартово добуток, потім групує. На 10 000 елементів проміжна виборка може скласти мільйони рядків.
Оптимізація: розбити на два запити — спочатку отримати основні дані, потім підгрузити значення множественних властивостей окремим запитом за масивом ID.
Оптимізація на рівні запитів
Вивести фільтрацію до JOIN. Якщо потрібні елементи певного розділу з конкретною властивістю — спочатку відфільтруйте за b_iblock_element (з індексом за IBLOCK_SECTION_ID), потім робіть JOIN. MySQL повинен почати з найменшої виборки.
Використовуйте STRAIGHT_JOIN щоб зафіксувати порядок з'єднання, якщо оптимізатор вибирає неправильний план:
$connection = \Bitrix\Main\Application::getConnection();
$result = $connection->query("
SELECT STRAIGHT_JOIN be.ID, be.NAME, p.VALUE
FROM b_iblock_element be
INNER JOIN b_iblock_element_property p
ON p.IBLOCK_ELEMENT_ID = be.ID AND p.IBLOCK_PROPERTY_ID = 42
WHERE be.IBLOCK_ID = 5
AND be.ACTIVE = 'Y'
AND p.VALUE_NUM BETWEEN 1000 AND 5000
ORDER BY be.SORT
LIMIT 20
");
Заміна LEFT JOIN на підзапит або EXISTS. Для перевірки наявності зв'язку (потрібне тільки значення або тільки факт наявності) EXISTS працює швидше, ніж LEFT JOIN з IS NOT NULL:
-- Повільніше: JOIN + групування
SELECT be.* FROM b_iblock_element be
LEFT JOIN b_iblock_element_property p ON p.IBLOCK_ELEMENT_ID = be.ID AND p.IBLOCK_PROPERTY_ID = 10
WHERE p.ID IS NOT NULL;
-- Швидше: EXISTS
SELECT be.* FROM b_iblock_element be
WHERE EXISTS (
SELECT 1 FROM b_iblock_element_property p
WHERE p.IBLOCK_ELEMENT_ID = be.ID AND p.IBLOCK_PROPERTY_ID = 10
);
Оптимізація через D7 ORM
D7 дозволяє контролювати які таблиці включаються в JOIN через параметр runtime та явну налаштування relations. При роботі з HL-інфоблоками (Highload), D7 генерує чистіші запити без зайвих JOIN до b_iblock_element_property.
// Уникаємо JOIN до таблиці властивостей, працюємо напрямик з HL-таблицею
$result = \Bitrix\Highloadblock\HighloadBlockTable::compileEntity($hlBlock)
->getDataClass()::getList([
'select' => ['ID', 'UF_PRODUCT_ID', 'UF_PRICE'],
'filter' => ['>=UF_PRICE' => 1000, '<=UF_PRICE' => 5000],
'limit' => 100,
]);
HL-інфоблоки зберігають дані в окремій таблиці без JOIN до b_iblock_element_property — для високонавантажених властивостей (технічні характеристики, великі каталоги) це правильна архітектура.
Індекси для оптимізації JOIN
| Таблиця | Індекс | Для чого |
|---|---|---|
b_iblock_element_property |
(IBLOCK_PROPERTY_ID, VALUE_NUM) |
Фільтр за числовими властивостями |
b_iblock_element_property |
(IBLOCK_ELEMENT_ID, IBLOCK_PROPERTY_ID) |
JOIN за елементом + властивістю |
b_uts_iblock_N_single |
(UF_CUSTOM_FIELD) |
Фільтр за користувацькою властивістю |
b_catalog_price |
(PRODUCT_ID, CATALOG_GROUP_ID) |
JOIN цін до елементів |
b_iblock_section_element |
(IBLOCK_SECTION_ID) |
JOIN розділів до елементів |
Тривалість робіт
| Задача | Тривалість | Ефект |
|---|---|---|
| EXPLAIN-аналіз JOIN-запитів, додавання індексів | 2–3 дні | Прискорення 5–20x на проблемних запитах |
| Рефакторинг виборки властивостей (розділення на підзапити) | 3–5 днів | Усунення декартова добутку |
| Перевід навантажених властивостей на HL-інфоблоки | 1–2 тижні | Усунення JOIN до b_iblock_element_property |
| Комплексна оптимізація каталогу | 2–3 тижні | Сторінка каталогу < 100 мс замість 2–5 с |
JOIN-запити у Бітриксі — наслідок архітектурного рішення зберігати всі властивості у універсальній таблиці. При невеликих обсягах це працює. При зростанні даних потрібно або додавати індекси, або змінювати схему зберігання на HL-інфоблоки або власні таблиці.







