Генерация тестовых данных, быстрый поиск и правильная индексация UF_NAME

<?php
use Bitrix\Main;
use Bitrix\Main\Loader;
use Bitrix\Highloadblock as HL;
use Bitrix\Main\Type\DateTime;
/**
* Класс-утилита для работы с HL-блоком Test1.
* Демонстрирует полный цикл: создание, наполнение, выборки, индексация.
*/
class Test1Helper
{
/** Создаёт HL-блок Test1 (UF_NAME varchar(255), UF_TIME datetime). */
public static function createTestHLBlock(): bool
{
if (!Loader::includeModule('highloadblock')) {
return false;
}
// Уже существует?
$exists = HL\HighloadBlockTable::getList([
'filter' => ['=NAME' => 'Test1'],
])->fetch();
if ($exists) {
return true;
}
// Создаём HL-блок
$res = HL\HighloadBlockTable::add([
'NAME' => 'Test1',
'TABLE_NAME' => 'testio_test1',
]);
if (!$res->isSuccess()) {
return false;
}
$hlId = (int)$res->getId();
$ute = new \CUserTypeEntity();
// UF_NAME (varchar(255) => подходит для индекса)
$ute->Add([
'ENTITY_ID' => "HLBLOCK_{$hlId}",
'FIELD_NAME' => 'UF_NAME',
'USER_TYPE_ID' => 'string',
'MULTIPLE' => 'N',
'MANDATORY' => 'Y',
'SHOW_IN_LIST' => 'Y',
'EDIT_IN_LIST' => 'Y',
'SORT' => 100,
'SETTINGS' => [
'DEFAULT_VALUE' => '',
'SIZE' => 20,
'ROWS' => 1,
'MAX_LENGTH' => 255, // varchar(255)
],
'EDIT_FORM_LABEL'=> ['ru' => 'Имя', 'en' => 'Name'],
]);
// UF_TIME
$ute->Add([
'ENTITY_ID' => "HLBLOCK_{$hlId}",
'FIELD_NAME' => 'UF_TIME',
'USER_TYPE_ID' => 'datetime',
'MULTIPLE' => 'N',
'MANDATORY' => 'Y',
'SHOW_IN_LIST' => 'Y',
'EDIT_IN_LIST' => 'Y',
'SORT' => 200,
'SETTINGS' => [
'DEFAULT_VALUE' => ['TYPE' => 'NONE', 'VALUE' => ''],
],
'EDIT_FORM_LABEL'=> ['ru' => 'Время', 'en' => 'Time'],
]);
return true;
}
/** Создаёт HL-блок (если нет) и заполняет случайными данными. */
public static function createAndFillTestHLBlock(int $count = 100): bool
{
if (!self::createTestHLBlock()) {
return false;
}
self::fillTestHLBlock($count);
self::createIndexForUfName(); // сразу ставим индекс
return true;
}
/** Массовое добавление тестовых записей. */
public static function fillTestHLBlock(int $count = 100): void
{
if (!Loader::includeModule('highloadblock')) {
return;
}
$hl = HL\HighloadBlockTable::getList([
'filter' => ['=NAME' => 'Test1'],
])->fetch();
if (!$hl) {
return;
}
$dataClass = HL\HighloadBlockTable::compileEntity($hl)->getDataClass();
for ($i = 0; $i < $count; $i++) {
$dataClass::add([
'UF_NAME' => 'Test Name ' . mt_rand(1000, 9999),
'UF_TIME' => (new DateTime())->add('-' . mt_rand(1, 720) . ' hours'),
]);
}
}
/** Получает все записи, моложе $hours часов. */
public static function getRecentRecordsByHours(int $hours = 24): array
{
if (!Loader::includeModule('highloadblock')) {
return [];
}
$hl = HL\HighloadBlockTable::getList([
'filter' => ['=NAME' => 'Test1'],
])->fetch();
if (!$hl) {
return [];
}
$dataClass = HL\HighloadBlockTable::compileEntity($hl)->getDataClass();
$border = (new DateTime())->add('-' . abs($hours) . ' hours');
return $dataClass::getList([
'filter' => ['>UF_TIME' => $border],
'order' => ['UF_TIME' => 'DESC'],
])->fetchAll();
}
/** Получает одну случайную запись (способ №1: OFFSET). */
public static function getRandomRecord(): ?array
{
if (!Loader::includeModule('highloadblock')) {
return null;
}
$hl = HL\HighloadBlockTable::getList([
'filter' => ['=NAME' => 'Test1'],
])->fetch();
if (!$hl) {
return null;
}
$dataClass = HL\HighloadBlockTable::compileEntity($hl)->getDataClass();
$total = $dataClass::getCount();
if ($total === 0) {
return null;
}
$offset = mt_rand(0, $total - 1);
return $dataClass::getList([
'limit' => 1,
'offset' => $offset,
])->fetch() ?: null;
}
/** Альтернативный случайный выбор (способ №2: RAND() в runtime). */
public static function getRandomRecordByRand(): ?array
{
if (!Loader::includeModule('highloadblock')) {
return null;
}
$hl = HL\HighloadBlockTable::getList([
'filter' => ['=NAME' => 'Test1'],
])->fetch();
if (!$hl) {
return null;
}
$dataClass = HL\HighloadBlockTable::compileEntity($hl)->getDataClass();
return $dataClass::getList([
'runtime' => [
'RAND' => [
'data_type' => 'float',
'expression' => ['RAND()'],
],
],
'order' => ['RAND' => 'ASC'],
'limit' => 1,
'select' => ['ID', 'UF_NAME', 'UF_TIME'],
])->fetch() ?: null;
}
/** Создаёт индекс по UF_NAME, если ещё не создан. */
public static function createIndexForUfName(): void
{
if (!Loader::includeModule('highloadblock')) {
return;
}
$hl = HL\HighloadBlockTable::getList([
'filter' => ['=NAME' => 'Test1'],
])->fetch();
if (!$hl) {
return;
}
$conn = Main\Application::getConnection();
$table = $conn->getSqlHelper()->quote($hl['TABLE_NAME']);
$exists = $conn->query("
SHOW INDEX FROM {$table} WHERE Column_name = 'UF_NAME'
")->fetch();
if (!$exists) {
$conn->queryExecute("
ALTER TABLE {$table} ADD INDEX idx_uf_name (`UF_NAME`(255))
");
}
}
}
Введение
Highload-блоки (HL-blocks) в 1С-Битрикс — незаменимый инструмент, когда нужно хранить и обрабатывать большие объёмы структурированных данных быстрее, чем это позволяет стандартный iblock. В данной статье мы:
- создаём тестовый HL-блок Test1 с двумя полями:
UF_NAME
иUF_TIME
; - генерируем случайные данные для нагрузочного тестирования;
- получаем свежие или случайные записи двумя способами;
- оптимизируем поиск добавлением индекса на
UF_NAME
, предварительно меняя тип поля сtext
наvarchar(255)
— критически важно для MySQL/MariaDB.
Материал пригодится как начинающим, так и опытным разработчикам, занимающимся оптимизацией производительности 1С-Битрикс.
Что делает каждая часть кода
Метод | Назначение | Ключевые моменты |
---|---|---|
createTestHLBlock() |
Создаёт HL-блок Test1 (если ещё не существует) | Сразу задаём UF_NAME как varchar(255) для будущего индекса |
createAndFillTestHLBlock() |
Обёртка: создаёт блок и заполняет его | После заполнения сразу создаётся индекс |
fillTestHLBlock($count) |
Добавляет $count случайных записей |
Используется DateTime()->add('-N hours') для реалистичного параметра времени |
getRecentRecordsByHours($hours) |
Возвращает записи свежее $hours часов |
Удобно для протухающих данных (кэш, лог) |
getRandomRecord() |
Случайная запись через offset |
Быстро на малых объёмах, но O(n) на больших |
getRandomRecordByRand() |
Случайная запись через RAND() runtime-поле |
Рекомендуется при больших объёмах данных |
createIndexForUfName() |
Добавляет индекс idx_uf_name |
Проверяет наличие перед добавлением |
Почему varchar(255)
, а не text
- MySQL не индексирует
TEXT
напрямую; приходится указывать префикс. - Записи с
TEXT
хранятся вне основного ряда таблицы (off-row), что замедляет чтение. varchar(255)
помещает строку целиком внутри row-id, а индексация делается без префикса или с коротким префиксом автоматически.
Примеры использования
1. Инициализация и наполнение
// где-нибудь в admin-скрипте или миграции
Test1Helper::createAndFillTestHLBlock(250); // создаём и добавляем 250 записей
2. Выбор свежих записей (за последние 12 часов)
$recent = Test1Helper::getRecentRecordsByHours(12);
foreach ($recent as $row) {
echo $row['UF_NAME'] . ' — ' . $row['UF_TIME']->format('d.m.Y H:i') . PHP_EOL;
}
3. Получение случайной записи
// Способ №1 (offset)
$random1 = Test1Helper::getRandomRecord();
// Способ №2 (RAND() runtime) — предпочтительный
$random2 = Test1Helper::getRandomRecordByRand();
4. Индексация поля UF_NAME отдельно
Если HL-блок был создан раньше и UF_NAME
имеет тип text
, сначала поменяйте тип вручную (или через миграцию) на varchar(255)
, затем вызовите:
Test1Helper::createIndexForUfName();
Заключение
Использование Highload-блоков с корректно настроенными индексами значительно повышает производительность выборок в 1С-Битрикс, особенно при больших объёмах данных.
Приведённый класс демонстрирует полный цикл: создание, массовую вставку, оптимизацию и различные сценарии выборки, включая референс для случайного чтения через RAND()
— популярный при реализации виджетов «случайный отзыв», «товар дня» и т. д.
Интегрируйте решения из статьи в свои миграции или модули, и работа вашего проекта на Bitrix станет заметно быстрее и стабильнее.
Изучите также базовую информацию - Работа с Highload-блоками в Битрикс D7