Оптимізація запитів до 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 — без нього неможливо зрозуміти, чи потрібен індекс і який саме.







