Highload-блоки окружены двумя противоположными мифами. Первый: «это волшебная таблетка, всё нужно перенести туда». Второй: «это неудобная штука без нормальных компонентов, лучше не связываться». Оба неверны, и оба ведут к плохим решениям.
Правда в том, что HL-блок — это просто плоская таблица MySQL с ORM-обёрткой и админкой. Всё, что вы про них знаете про обычные таблицы, применимо; всё, чем удобны инфоблоки, — отсутствует. Разберём, где эта сделка выгодна.
Что такое Highload-блоки
Highload-блоки (HL-блоки) — это альтернатива инфоблокам для хранения данных в Битрикс. Главное отличие: каждый HL-блок хранится в отдельной таблице MySQL с плоской структурой.
Инфоблоки:
- Данные в общих таблицах
b_iblock_element,b_iblock_element_property - EAV-модель (Entity-Attribute-Value) — свойства хранятся построчно
- Универсальные, но медленные на больших объёмах
Highload-блоки:
- Своя таблица для каждого блока
- Каждое свойство — отдельная колонка
- Быстрые, но менее гибкие
Когда использовать HL-блоки: справочники (города, бренды), логи, временные данные, счётчики, любые данные с простой структурой и большим объёмом (100k+ записей).
Сравнение производительности
Разница в скорости реальна, но её постоянно преувеличивают, потому что сравнивают с худшим вариантом инфоблока. Разберём честно.
Инфоблок с первым вариантом хранения свойств держит все значения в общей таблице b_iblock_element_property — строка на каждое значение каждого свойства. Выборка с фильтром по нескольким свойствам разворачивается в несколько JOIN к таблице, где лежат значения всего сайта. Вот здесь HL-блок выигрывает в разы.
Инфоблок со вторым вариантом хранения (VERSION = 2) устроен принципиально иначе: у каждого инфоблока своя таблица b_iblock_element_prop_s{ID}, где каждое свойство — отдельная колонка. То есть структурно это почти то же самое, что HL-блок. Разница на выборках здесь уже не «в 10–50 раз», а в единицы десятков процентов — за счёт одного JOIN к b_iblock_element и меньшего количества служебных полей.
Практический вывод. Прежде чем переносить инфоблок в HL-блок ради скорости, проверьте SELECT ID, VERSION FROM b_iblock и переключите хранилище на второй вариант, если стоит первый. Это делается в настройках инфоблока, занимает несколько минут и часто снимает вопрос производительности целиком — без переписывания кода и потери штатных компонентов.
Переезд на HL-блок оправдан не столько скоростью, сколько тем, что вам не нужны разделы, ЧПУ, поиск и права доступа, а нужны быстрая запись и простая структура.
Создание Highload-блока
Через админку
Админка → Контент → Highload-блоки → Добавить.
- Название — человекочитаемое (Города)
- Символьный код — для API (Cities)
- Таблица — имя в БД (b_hlbd_cities)
Через код
<?php
use Bitrix\Highloadblock\HighloadBlockTable;
$result = HighloadBlockTable::add([
'NAME' => 'Cities',
'TABLE_NAME' => 'b_hlbd_cities',
]);
if ($result->isSuccess()) {
$hlblockId = $result->getId();
// Добавляем поля
$userField = new \CUserTypeEntity();
// Название города
$userField->Add([
'ENTITY_ID' => 'HLBLOCK_' . $hlblockId,
'FIELD_NAME' => 'UF_NAME',
'USER_TYPE_ID' => 'string',
'MANDATORY' => 'Y',
'EDIT_FORM_LABEL' => ['ru' => 'Название'],
'LIST_COLUMN_LABEL' => ['ru' => 'Название'],
]);
// Код региона
$userField->Add([
'ENTITY_ID' => 'HLBLOCK_' . $hlblockId,
'FIELD_NAME' => 'UF_REGION_CODE',
'USER_TYPE_ID' => 'string',
'MANDATORY' => 'N',
]);
// Население (число)
$userField->Add([
'ENTITY_ID' => 'HLBLOCK_' . $hlblockId,
'FIELD_NAME' => 'UF_POPULATION',
'USER_TYPE_ID' => 'integer',
'MANDATORY' => 'N',
]);
}Работа с данными (D7 API)
Получение класса сущности
compileEntity() — не бесплатный вызов. Он собирает описание сущности и объявляет PHP-класс на лету. Делать это в цикле или в каждом методе репозитория — заметная лишняя работа. Получайте класс один раз и держите в статическом свойстве или в контейнере.
<?php
use Bitrix\Main\Loader;
use Bitrix\Highloadblock\HighloadBlockTable;
Loader::includeModule('highloadblock');
// По ID
$hlblock = HighloadBlockTable::getById(1)->fetch();
// По имени
$hlblock = HighloadBlockTable::getList([
'filter' => ['NAME' => 'Cities']
])->fetch();
// Получаем класс для работы с данными
$entity = HighloadBlockTable::compileEntity($hlblock);
$entityClass = $entity->getDataClass();
// Теперь $entityClass — это полноценный DataManager
// Например: CityTable (автогенерируемое имя)CRUD операции
<?php
// CREATE — добавление
$result = $entityClass::add([
'UF_NAME' => 'Москва',
'UF_REGION_CODE' => '77',
'UF_POPULATION' => 12600000,
]);
if ($result->isSuccess()) {
$id = $result->getId();
}
// READ — чтение
$cities = $entityClass::getList([
'select' => ['ID', 'UF_NAME', 'UF_POPULATION'],
'filter' => ['>=UF_POPULATION' => 1000000],
'order' => ['UF_POPULATION' => 'DESC'],
'limit' => 10,
])->fetchAll();
// Одна запись по ID
$city = $entityClass::getById(1)->fetch();
// UPDATE — обновление
$result = $entityClass::update($id, [
'UF_POPULATION' => 12700000,
]);
// DELETE — удаление
$result = $entityClass::delete($id);Фильтрация и сортировка
<?php
// Сложный фильтр
$cities = $entityClass::getList([
'filter' => [
'LOGIC' => 'OR',
[
'>=UF_POPULATION' => 1000000,
],
[
'UF_REGION_CODE' => '77', // Москва
],
],
'order' => ['UF_NAME' => 'ASC'],
])->fetchAll();
// Подсчёт
$count = $entityClass::getCount([
'>=UF_POPULATION' => 500000,
]);
// Группировка и агрегация
$stats = $entityClass::getList([
'select' => [
'UF_REGION_CODE',
new \Bitrix\Main\Entity\ExpressionField('TOTAL', 'COUNT(*)'),
new \Bitrix\Main\Entity\ExpressionField('AVG_POP', 'AVG(%s)', ['UF_POPULATION']),
],
'group' => ['UF_REGION_CODE'],
])->fetchAll();Практические примеры
Пример 1: Лог действий пользователей
<?php
// Создаём HL-блок UserActionsLog с полями:
// UF_USER_ID (integer)
// UF_ACTION (string)
// UF_DATA (text)
// UF_CREATED_AT (datetime)
class UserActionLogger
{
private static $entityClass = null;
public static function getEntityClass(): string
{
if (self::$entityClass === null) {
$hlblock = HighloadBlockTable::getList([
'filter' => ['NAME' => 'UserActionsLog']
])->fetch();
$entity = HighloadBlockTable::compileEntity($hlblock);
self::$entityClass = $entity->getDataClass();
}
return self::$entityClass;
}
public static function log(int $userId, string $action, array $data = []): void
{
$class = self::getEntityClass();
$class::add([
'UF_USER_ID' => $userId,
'UF_ACTION' => $action,
'UF_DATA' => json_encode($data, JSON_UNESCAPED_UNICODE),
'UF_CREATED_AT' => new \Bitrix\Main\Type\DateTime(),
]);
}
public static function getUserActions(int $userId, int $limit = 100): array
{
$class = self::getEntityClass();
return $class::getList([
'filter' => ['UF_USER_ID' => $userId],
'order' => ['UF_CREATED_AT' => 'DESC'],
'limit' => $limit,
])->fetchAll();
}
}
// Использование
UserActionLogger::log($USER->GetID(), 'order.create', ['order_id' => 12345]);Пример 2: Справочник брендов для фильтра
<?php
// HL-блок Brands:
// UF_NAME, UF_CODE, UF_LOGO, UF_SORT, UF_ACTIVE
class BrandRepository
{
private string $entityClass;
public function __construct()
{
$hlblock = HighloadBlockTable::getList([
'filter' => ['NAME' => 'Brands']
])->fetch();
$entity = HighloadBlockTable::compileEntity($hlblock);
$this->entityClass = $entity->getDataClass();
}
public function getActiveList(): array
{
$class = $this->entityClass;
return $class::getList([
'filter' => ['UF_ACTIVE' => true],
'order' => ['UF_SORT' => 'ASC', 'UF_NAME' => 'ASC'],
'cache' => ['ttl' => 3600, 'cache_joins' => true],
])->fetchAll();
}
public function getByCode(string $code): ?array
{
$class = $this->entityClass;
return $class::getList([
'filter' => ['UF_CODE' => $code, 'UF_ACTIVE' => true],
'limit' => 1,
])->fetch() ?: null;
}
}Ограничение: HL-блоки не поддерживают SEO-URL из коробки, множественные свойства работают через сериализацию, нет встроенных связей между блоками. Для сложных структур используйте инфоблоки.
Оптимизация
Индексы
Это тот пункт, где HL-блоки чаще всего разочаровывают: Битрикс не создаёт индексы за вас. Он создаёт колонку, и всё. Таблица на миллион записей с фильтром по неиндексированному полю ведёт себя ровно так, как ведёт себя любая таблица MySQL в этой ситуации, — и «быстрые HL-блоки» внезапно оказываются медленнее инфоблока, у которого нужные индексы ядро создало само.
-- Добавляем индекс вручную
ALTER TABLE b_hlbd_cities ADD INDEX idx_region (UF_REGION_CODE);
ALTER TABLE b_hlbd_cities ADD INDEX idx_population (UF_POPULATION);
-- Составной индекс
ALTER TABLE b_hlbd_user_actions ADD INDEX idx_user_date (UF_USER_ID, UF_CREATED_AT);Кэширование
<?php
// Встроенное кэширование D7
$items = $entityClass::getList([
'filter' => [...],
'cache' => [
'ttl' => 3600, // 1 час
'cache_joins' => true,
],
])->fetchAll();
// Или через Bitrix Cache
$cache = \Bitrix\Main\Data\Cache::createInstance();
if ($cache->initCache(3600, 'brands_list')) {
$brands = $cache->getVars()['brands'];
} elseif ($cache->startDataCache()) {
$brands = (new BrandRepository())->getActiveList();
$cache->endDataCache(['brands' => $brands]);
}Когда НЕ использовать HL-блоки
- Нужен SEO URL (ЧПУ)
- Сложная иерархия (разделы, подразделы)
- Множественные свойства
- Связи между элементами
- Нужен поиск по контенту
- Интеграция со стандартными компонентами Битрикс
К этому списку добавлю то, что обычно всплывает уже в процессе.
Права доступа. У инфоблока они есть на уровне ядра: группы, права на чтение и запись, ограничения по разделам. У HL-блока — только то, что вы напишете сами. Для справочника брендов это неважно, для данных, которые не должны видеть все, — критично.
Изменение структуры на живых данных. Добавление поля в HL-блок — это ALTER TABLE. На таблице в несколько миллионов строк операция блокирует запись и выполняется минутами. Инфоблок в этом смысле удобнее: новое свойство не трогает структуру таблиц.
Резервное копирование и перенос между окружениями. HL-блок — это запись в служебной таблице плюс отдельная таблица данных плюс набор пользовательских полей в b_user_field. Перенести его на другой сервер «просто выгрузив таблицу» не получится: без описания полей вы получите таблицу без сущности. Учитывайте это в миграциях.
Выборка без limit. ORM с радостью вернёт вам миллион записей в память. Для инфоблоков привычка ставить nTopCount выработана давно, для HL-блоков — почему-то нет.
Итоги
| Критерий | Инфоблоки | HL-блоки |
|---|---|---|
| Производительность | Средняя | Высокая |
| Гибкость | Высокая | Низкая |
| SEO | Есть | Нет |
| Разделы | Есть | Нет |
| Множественные свойства | Нативно | Через сериализацию |
| Для справочников | Не рекомендуется | Идеально |
| Для каталога | Идеально | Не рекомендуется |
Правило выбора, к которому всё сводится: инфоблок — для контента, HL-блок — для данных.
Контент — это то, у чего есть страница, адрес, права доступа и место в структуре сайта: товары, статьи, разделы. Данные — это то, что читается и пишется кодом: справочники, логи, счётчики, промежуточные результаты интеграций. Инфоблок оплачивает свою универсальность производительностью, HL-блок оплачивает производительность отсутствием всего остального.
И два практических напоминания напоследок. Индексы в HL-блоках создаёте вы, а не Битрикс, — без них весь смысл теряется. А прежде чем переносить существующий инфоблок ради скорости, проверьте вариант хранения его свойств: возможно, задача решается галочкой в настройках вместо недели работы.
Частые вопросы
Что такое highload-блок в Битриксе?
Highload-блок — это плоская таблица MySQL с ORM-обёрткой и админкой: каждый блок живёт в отдельной таблице, а каждое свойство — отдельная колонка. Инфоблоки для сравнения хранят данные в общих таблицах b_iblock_element и b_iblock_element_property по EAV-модели, где каждое значение каждого свойства — отдельная строка.
Насколько HL-блоки быстрее инфоблоков?
Зависит от варианта хранения свойств инфоблока. Против 1-го варианта, где все значения лежат в общей таблице b_iblock_element_property и выборка разворачивается в несколько JOIN, HL-блок выигрывает в разы. Против 2-го варианта, где у каждого инфоблока своя таблица с колонками, разница уже единицы десятков процентов — структурно это почти то же самое.
Стоит ли переносить инфоблок в HL-блок ради скорости?
Сначала проверьте вариант хранения запросом SELECT ID, VERSION FROM b_iblock. Если стоит первый, переключите на второй в настройках инфоблока — это несколько минут и часто снимает вопрос производительности целиком, без переписывания кода и потери штатных компонентов. Переезд на HL-блок оправдан не столько скоростью, сколько тем, что вам не нужны разделы, ЧПУ, поиск и права доступа, а нужны быстрая запись и простая структура.
Создаёт ли Битрикс индексы в HL-блоках автоматически?
Нет. Битрикс создаёт колонку, и всё. Таблица на миллион записей с фильтром по неиндексированному полю ведёт себя ровно так, как любая таблица MySQL в этой ситуации, — и «быстрые HL-блоки» внезапно оказываются медленнее инфоблока, у которого нужные индексы ядро создало само. Индексы добавляют вручную через ALTER TABLE, в том числе составные.
Когда HL-блоки использовать не стоит?
Когда нужны ЧПУ, иерархия разделов, множественные свойства, связи между элементами, поиск по контенту или интеграция со штатными компонентами. К этому добавьте права доступа (у HL-блока их нет на уровне ядра, только то, что напишете сами), изменение структуры на живых данных (добавление поля — это ALTER TABLE, который на миллионах строк блокирует запись минутами) и перенос между окружениями: HL-блок — это запись в служебной таблице плюс таблица данных плюс набор полей в b_user_field, выгрузкой одной таблицы его не перенести.
Что выбрать: инфоблок или highload-блок?
Инфоблок — для контента, HL-блок — для данных. Контент — это то, у чего есть страница, адрес, права доступа и место в структуре сайта: товары, статьи, разделы. Данные — это то, что читается и пишется кодом: справочники, логи, счётчики, промежуточные результаты интеграций. Инфоблок оплачивает свою универсальность производительностью, HL-блок оплачивает производительность отсутствием всего остального.