Storage — это низкоуровневый класс ядра 1С-Битрикс, отвечающий за хранение и поддержку фасетного индекса свойств инфоблока. В статье мы разберём каждую возможность класса и покажем полностью рабочие примеры кода, которые можно вставить в свои скрипты или модули.

1. Зачем нужен фасетный индекс?
- Быстрые фильтры в каталогах и умном фильтре
- Поддержка сортировки по цене и числовым свойствам
- Мгновенные выборки без «джойнов» по большому числу свойств
Без индекса каждый запрос к каталогу превращается в множественные JOIN-операции к таблицам b_iblock_element_property*
, что заметно замедляет выборку.
2. Быстрый старт
<?php
use Bitrix\Iblock\PropertyIndex\Storage;
use Bitrix\Main\Loader;
Loader::includeModule('iblock');
$iblockId = 12;
$storage = new Storage($iblockId);
// 1. Проверяем, есть ли индекс
if (!$storage->isExists()) {
$storage->create();
}
// 2. Добавляем запись (пример для цены)
$sectionId = 44;
$elementId = 1001;
$priceFacetId = Storage::priceIdToFacetId(1); // ID базовой цены
$dictionaryValue = 0; // для цены всегда 0
$price = 2999.90;
$includeSubsections = true;
$storage->addIndexEntry(
$sectionId,
$elementId,
$priceFacetId,
$dictionaryValue,
$price,
$includeSubsections
);
Совет: при массовом импорте используйте batch-методы (
queueIndexEntry
→flushIndexEntries
), чтобы сократить время вставки.
3. Обзор методов и примеры
3.1 create()
— создание индекса
if (!$storage->isExists()) {
$storage->create();
}
Метод создаёт таблицу вида b_iblock_<IBLOCK_ID>_index
и накладывает оптимальные индексы SQL.
Ошибка, которую часто допускают: создавать индекс повторно. Всегда проверяйте
isExists()
.
3.2 addIndexEntry()
— одиночная вставка
$facetId = Storage::propertyIdToFacetId($propertyId);
$storage->addIndexEntry(
$sectionId,
$elementId,
$facetId,
$dictionaryValue, // либо ID значения, либо 0
$propertyNumber, // числовое значение или цена
$includeSubsections // true|false
);
- Для строковых/списочных свойств указываем
$value
— ID из словаря (b_iblock_dictionary
), а$valueNum = 0
. - Для числовых / цен — наоборот:
$value = 0
,$valueNum
— фактическое число.
3.3 Пакетная вставка: queueIndexEntry()
+ flushIndexEntries()
$items = getMyImportArray(); // массив элементов
$storage = new Storage($iblockId);
foreach ($items as $row) {
$storage->queueIndexEntry(
$row['SECTION_ID'],
$row['ELEMENT_ID'],
Storage::propertyIdToFacetId($row['PROP_ID']),
$row['DICT_ID'],
0,
false
);
}
$storage->flushIndexEntries();
- Используйте, если вставляете >1000 записей за один проход.
- Внутри класса стоит лимит
$insertMax = 1024000
байт, после которого буфер пишется автоматически.
3.4 deleteIndexElement()
— удаление записей элемента
$storage->deleteIndexElement($elementId);
Полезно при доиндексации после обновления свойств отдельного товара.
3.5 drop()
— полное удаление индекса
if ($storage->isExists()) {
$storage->drop();
}
Используйте, если нужно пересобрать индекс с нуля (например, после массового удаления свойств).
3.6 Служебные методы преобразования идентификаторов
// Свойство → Facet
$facetId = Storage::propertyIdToFacetId($propertyId);
// Цена → Facet
$facetId = Storage::priceIdToFacetId($priceId);
// Facet → Свойство
$propertyId = Storage::facetIdToPropertyId($facetId);
// Facet → Цена
$priceId = Storage::facetIdToPriceId($facetId);
// Проверки
if (Storage::isPriceId($facetId)) { /* ... */ }
if (Storage::isPropertyId($facetId)) { /* ... */ }
Почему чёт/нечёт?
Внутри класса идентификаторы свойств хранятся как чётные числа (
propertyId * 2
), а цен — нечётные (priceId * 2 + 1
). Это даёт мгновенную проверку принадлежности.
4. Практические сценарии
4.1 Генерация индекса после импорта каталога
function buildFacetIndex(int $iblockId): void
{
$storage = new Storage($iblockId);
if ($storage->isExists()) {
$storage->drop(); // начнём с чистого листа
}
$storage->create();
$rsElements = CIBlockElement::GetList(
[],
['IBLOCK_ID' => $iblockId, 'ACTIVE' => 'Y'],
false,
false,
['ID', 'IBLOCK_SECTION_ID']
);
while ($arElement = $rsElements->Fetch()) {
indexProduct($storage, $arElement);
}
$storage->flushIndexEntries();
}
function indexProduct(Storage $storage, array $arElement): void
{
$elementId = (int)$arElement['ID'];
$sectionId = (int)$arElement['IBLOCK_SECTION_ID'];
// индексируем цены
$price = CPrice::GetBasePrice($elementId);
if ($price) {
$storage->queueIndexEntry(
$sectionId,
$elementId,
Storage::priceIdToFacetId($price['CATALOG_GROUP_ID']),
0,
(float)$price['PRICE'],
true
);
}
// индексируем свойства
$props = CIBlockElement::GetPropertyValuesArray(
$arValues,
$storage->getIblockId(),
['ID' => $elementId]
);
foreach ($arValues[$elementId] as $propertyId => $prop) {
if ($prop['PROPERTY_TYPE'] === 'N') {
// числовое
$storage->queueIndexEntry(
$sectionId,
$elementId,
Storage::propertyIdToFacetId($propertyId),
0,
(float)$prop['VALUE'],
true
);
} elseif ($prop['PROPERTY_TYPE'] === 'L') {
// списочное
$storage->queueIndexEntry(
$sectionId,
$elementId,
Storage::propertyIdToFacetId($propertyId),
(int)$prop['VALUE_ENUM_ID'],
0,
true
);
}
}
}
Скрипт создаёт индекс «с нуля», обрабатывая каждую цену и свойство согласно типу.
4.2 Использование индекса в своём компоненте фильтра
$facet = new Bitrix\Iblock\PropertyIndex\Facet($iblockId);
// получаем элементы, у которых цена до 3000
$result = $facet->query([
['FACET_ID' => Storage::priceIdToFacetId(1), '<=VALUE_NUM' => 3000]
]);
while ($row = $result->fetch()) {
echo 'Элемент ' . $row['ELEMENT_ID'] . PHP_EOL;
}
PropertyIndex\Facet
опирается на данные таблицыStorage
, поэтому предварительное создание индекса — обязательное условие.
5. Полезные советы и подводные камни
Совет | Пояснение |
---|---|
Создавайте индекс офлайн | При больших каталогах (>50 000 товаров) выполняйте create() и заполнение на тестовом стенде или скриптом cron в «окно» минимальной нагрузки. |
Следите за SQL-буфером | Если меняете $insertMax , балансируйте между количеством соединений и размером запроса (max_allowed_packet в MySQL). |
Не смешивайте значения | Одно свойство = одна запись в индексе. Для множественных свойств добавляйте запись на каждое значение. |
Регулярно чистите индекс | После удаления товаров вызывайте deleteIndexElement() или пересоздавайте индекс, чтобы не накапливать «мёртвые» ссылки. |
6. Заключение
Storage
— мощный, но недооценённый инструмент в 1С-Битрикс. Он позволяет:
- ускорить фильтрацию каталога в разы;
- разгрузить базу за счёт отказа от тяжёлых JOIN-ов;
- легко масштабировать проект за счёт простого расширения индекса.
Используйте примеры из статьи как основу для своих скриптов импорта, cron-задач и компонентов. При грамотной настройке фасетный индекс служит надёжным фундаментом высокой скорости e-commerce-проектов.