Оптимизация JOIN-запросов в 1С-Битрикс

Наша компания занимается разработкой, поддержкой и обслуживанием решений на Битрикс и Битрикс24 любой сложности. От простых одностраничных сайтов до сложных интернет магазинов, CRM систем с интеграцией 1С и телефонии. Опыт разработчиков подтвержден сертификатами от вендора.
Предлагаемые услуги
Показано 1 из 1 услугВсе 1626 услуг
Оптимизация JOIN-запросов в 1С-Битрикс
Средняя
~1-2 недели
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1173
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Разработка на базе Битрикс, Битрикс24, 1С для компании Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Разработка на базе 1С Предприятие для компании МИРСАНБЕЛ
    745
  • image_crm_dolbimby_434_0.webp
    Разработка сайта на CRM Битрикс24 для компании DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Разработка на базе Битрикс24 для компании ТЕХНОТОРГКОМПЛЕКС
    976

Оптимизация 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-инфоблоки или собственные таблицы.