Почему Highload-блоки работают медленно?
Highload-блоки в Битрикс — удобный инструмент для хранения справочников, логов, настроек без иерархии. Но их производительность часто разочаровывает. Мы, команда сертифицированных Битрикс-разработчиков с 5+ лет опыта, регулярно сталкиваемся с проектами, где HL-блоки тормозят. Наш опыт показывает: в 90% случаев проблема — отсутствие индексов или неэффективное кеширование. Предлагаем полный аудит и ускорение под ключ — результат гарантируем.
Каждый HL-блок — отдельная MySQL-таблица с автогенерированным именем вида b_uts_catalog_props или пользовательским именем через настройки. В отличие от инфоблоков, HL-блоки не имеют сложных JOIN-зависимостей, и теоретически должны работать быстро. На практике медленные запросы встречаются не реже — из-за отсутствия индексов, неправильного использования API и неудачной схемы данных.
Как правильно индексировать HL-блоки?
При создании 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 мс на запрос.
Индексы создаются напрямую через SQL или через миграции. Для примера выше:
ALTER TABLE b_uts_my_highload ADD INDEX idx_status_created (UF_STATUS, UF_CREATED_AT); | Тип индекса | Когда использовать | Пример |
|---|---|---|
| Простой | Одно поле в filter | INDEX (UF_STATUS) |
| Составной | Несколько полей в filter/order | INDEX (UF_STATUS, UF_CREATED_AT) |
| Покрывающий | Все поля запроса включены | INDEX (UF_STATUS, UF_CREATED_AT, UF_NAME, UF_VALUE) |
Правила выбора индексов:
- Поля в
filter— кандидаты для индексирования, но порядок важен: поле с меньшей кардинальностью (например,UF_STATUSс 3 значениями) ставится первым только если оно используется в запросах самостоятельно; иначе составной индекс начинается с высококардинального поля. - Поля в
orderдолжны быть в индексе или покрывающем индексе. -
SELECT *заменяйте на явныйselectс нужными полями — сокращает объём данных, иногда позволяет использовать covering index.
Кеширование: как избежать повторных запросов?
Highload-блоки не имеют автоматического тегированного кеша, как инфоблоки. При использовании 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 | Деградирует при больших смещениях | Маленькие таблицы (<10 000 записей) |
| Курсорная (по ID) | Константное время | Большие таблицы, монотонная сортировка |
Стандартная пагинация через 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, ]); Это работает только при монотонном порядке сортировки по индексированному полю.
Из нашей практики: кейс с логами заказов
Один из наших клиентов — интернет-магазин с оборотом более 50 000 заказов в месяц — использовал HL-блок как лог событий заказа. В таблице было 3,2 млн записей, 8 полей. Запрос истории конкретного заказа (фильтр по UF_ORDER_ID) без индекса выполнялся 1,2 секунды. Мы добавили индекс:
ALTER TABLE b_uts_order_log ADD INDEX idx_order_id (UF_ORDER_ID); Время запроса упало с 1,2 с до 2 мс. Дополнительно мы вынесли сводную статистику за период в отдельный кешируемый отчёт, обновляемый ночью — это сняло нагрузку на MySQL в бизнес-часы.
Что входит в работу по оптимизации HL-блоков
- Аудит: анализ slow query log, панели производительности Битрикс, EXPLAIN всех тяжёлых запросов.
- Проектирование: подбор индексов, настройка кеширования, рефакторинг запросов.
- Реализация: создание индексов, внедрение ManagedCache, переход на курсорную пагинацию.
- Тестирование: замеры производительности до/после, проверка на боевых данных.
- Документация: передача схемы индексов, инструкций по поддержке.
Ориентировочные сроки: от 3 до 10 дней в зависимости от объёма данных и сложности схемы.
Когда стоит обратиться к специалистам?
Если вы замечаете, что страницы админки или каталога загружаются дольше 2 секунд, база данных «тупит» при работе со справочниками, или в slow query log постоянно фигурируют запросы к HL-блокам — пора проводить аудит. Мы гарантируем ускорение запросов минимум в 10 раз. Свяжитесь с нами, чтобы получить консультацию и предварительную оценку проекта.
Согласно Wikipedia, правильно подобранные индексы ускоряют поиск по таблицам до нескольких порядков. Наша команда подтверждает это на практике.







