Оптимизация запросов к Highload-блокам 1С-Битрикс
HighLoad-блоки (модуль highloadblock) появились в Битрикс как ответ на производительные проблемы инфоблоков при хранении больших объёмов данных без иерархии. Каждый HL-блок — это отдельная MySQL-таблица с автогенерированным именем вида b_uts_catalog_props или пользовательским именем через настройки. В отличие от инфоблоков, HL-блоки не имеют сложных JOIN-зависимостей, и теоретически должны работать быстро. На практике медленные запросы к HL-блокам встречаются не реже — из-за отсутствия индексов, неправильного использования API и неудачной схемы данных.
Архитектура HL-блоков и точки роста
Таблица HL-блока содержит колонки ID, UF_* (пользовательские поля). При создании HL-блока через интерфейс Битрикс никаких индексов, кроме первичного ключа по ID, автоматически не создаётся. Это означает, что любой фильтр по полям, кроме ID, приводит к полному перебору таблицы (type = ALL в EXPLAIN).
Пример проблемного запроса:
$result = HighloadBlockTable::getList([
'filter' => [
'=UF_STATUS' => 'active',
'>=UF_CREATED_AT' => new DateTime('-30 days'),
],
'select' => ['ID', 'UF_NAME', 'UF_VALUE'],
'order' => ['UF_CREATED_AT' => 'DESC'],
'limit' => 50,
]);
Без индекса по UF_STATUS и UF_CREATED_AT этот запрос сканирует всю таблицу. При 500 000 записей — 300–800 мс на запрос.
Индексы для HL-блоков
Битрикс не предоставляет UI для создания индексов HL-блоков. Индексы создаются напрямую через SQL или через миграции. Для примера выше:
ALTER TABLE b_uts_my_highload
ADD INDEX idx_status_created (UF_STATUS, UF_CREATED_AT);
Правила для выбора индексов:
- Поля в
filter— кандидаты для индексирования, но порядок важен: поле с меньшей кардинальностью (например,UF_STATUSс 3 значениями) ставится первым только если оно используется в запросах самостоятельно; иначе составной индекс начинается с высококардинального поля - Поля в
orderдолжны быть в индексе или покрывающем индексе -
SELECT *заменяется на явныйselectс нужными полями — сокращает объём данных, иногда позволяет использовать covering index
Кеширование результатов HL-запросов
HL-блоки не имеют автоматического тегированного кеша, как инфоблоки. При использовании HighloadBlockTable::getList() кеширование нужно добавлять явно через ManagedCache:
$cache = \Bitrix\Main\Application::getInstance()->getManagedCache();
$cacheKey = 'hl_my_block_active_' . md5(serialize($filter));
if (!$cache->read(600, $cacheKey, 'hl_my_block')) {
$result = MyHLTable::getList(['filter' => $filter, 'select' => $select]);
$data = $result->fetchAll();
$cache->set($cacheKey, $data);
} else {
$data = $cache->get($cacheKey);
}
Инвалидация по тегу hl_my_block вызывается в обработчике OnAfterAdd / OnAfterUpdate HL-блока.
Пагинация на больших таблицах
Стандартная пагинация через offset деградирует на больших данных: OFFSET 50000 LIMIT 50 вынуждает MySQL прочитать и отбросить 50 000 записей. Для HL-блоков с десятками тысяч записей и бесконечной прокруткой или большим числом страниц используется cursor-based пагинация:
// Вместо offset используем последний ID
$result = MyHLTable::getList([
'filter' => ['=UF_STATUS' => 'active', '>ID' => $lastId],
'order' => ['ID' => 'ASC'],
'limit' => 50,
]);
Это работает только при монотонном порядке сортировки по индексированному полю.
Кейс: хранение логов заказов в HL-блоке
Интернет-магазин использовал HL-блок как лог событий заказа: 3,2 млн записей, 8 полей. Запрос истории конкретного заказа (фильтр по UF_ORDER_ID) без индекса — 1,2 с. Добавление индекса по UF_ORDER_ID:
ALTER TABLE b_uts_order_log ADD INDEX idx_order_id (UF_ORDER_ID);
Время запроса: 1,2 с → 2 мс. Дополнительно: запросы сводной статистики за период (агрегация по UF_EVENT_TYPE за месяц) вынесены в отдельный кешируемый отчёт, обновляемый ночью — сняло нагрузку на MySQL в бизнес-часы.
Денормализация и HL-блоки как справочники
Часто HL-блоки используются как справочники (типы оплаты, статусы, регионы). При этом в основных запросах к инфоблоку делается JOIN с HL-блоком для получения текстового значения. Правильное решение — денормализация: хранить текстовое значение прямо в поле инфоблока, обновлять его при изменении справочника через агент. Это исключает JOIN в 95% запросов на чтение.
Этапы оптимизации
| Работы | Срок |
|---|---|
| Аудит запросов (slow query log, панель Битрикс) | 0,5–1 день |
| Добавление индексов | 0,5 дня |
| Переработка кеширования | 1–3 дня |
| Рефакторинг схемы (денормализация, cursor пагинация) | 2–5 дней |
Начинать всегда с EXPLAIN — без него невозможно понять, нужен ли индекс и какой именно.







