Блог разработчика 1С-Битрикс

Встроенный кеш ORM в Битриксе: cache в getList()

ORM в Битриксе (D7) позволяет кешировать результаты запросов к базе данных прямо в методах getList(), getByPrimary(), getRow() и других. Достаточно передать параметр cache — и система сама сформирует ключ кеша, сохранит результат и очистит его при изменении данных.

Встроенный кеш ORM в Битриксе: cache в getList()

1. Что такое ORM-кеш в Битриксе

В D7-ORM Битрикса (Bitrix\Main\ORM) запросы к таблицам делаются методами типа:

SomeTable::getList([...]);
SomeTable::getByPrimary($id, [...]);
SomeTable::getRow([...]);

Почти во всех этих методах можно передать параметр:

'cache' => [
    'ttl'         => 3600,  // время жизни кеша в секундах
    'cache_joins' => true,  // учитывать runtime-поля и Reference в формировании ключа кеша
]

ORM сам:

  • формирует SQL-запрос,
  • считает по нему ключ кеша (учитывая select, filter, order, runtime и другие параметры),
  • кладёт результат в управляемый кеш (папка /bitrix/managed_cache/MYSQL/orm_*),
  • автоматически очищает кеш при add/update/delete через соответствующий DataManager.

То есть вам не нужно городить Data\Cache — достаточно правильно настроить cache в самом запросе.

Важное ограничение: ORM-кеш не добавляет автоматически информацию о текущем пользователе или его правах доступа в ключ кеша. Если результаты запроса зависят от прав (например, фильтрация по группам доступа), обязательно включайте эти условия в параметры запроса (filter) или используйте другой механизм кеширования.

2. Базовый пример: кешируем список активных пользователей

Простейший случай — кешируем результат UserTable::getList():

 [
        'ID',
        'LOGIN',
        'NAME',
        'LAST_NAME',
        'EMAIL',
    ],
    'filter' => [
        '=ACTIVE' => 'Y',
    ],
    'order' => [
        'ID' => 'ASC',
    ],
    'limit' => 50,
    'cache' => [
        'ttl'         => 600,  // 10 минут
        'cache_joins' => true, // включать runtime-поля в ключ кеша
    ],
]);
$users = [];
while ($user = $result->fetch()) {
    $users[] = $user;
}
echo '
';
print_r($users);
echo '
';

Особенности:

  • Ключ кеша считается из всех параметров запроса (select, filter, order, limit, offset, runtime и т.д.).
  • TTL — в секундах.
  • Если вы добавите/обновите пользователя через UserTable::add() / ::update() / ::delete(), ORM сам сбросит кеш по этой сущности.

Внимание: При использовании offset (пагинация) каждая страница будет создавать отдельную запись в кеше. Учитывайте это при настройке TTL.

3. Своя сущность + кеш ORM: пример с CityTable

Чаще всего ORM-кеш используют со своими таблицами/HL-блоками. Сделаем пример сущности CityTable и запрос с кешем.

3.1. Описываем сущность

/local/modules/vendor.demo/lib/internals/citytable.php:

 true,
                'autocomplete' => true,
            ]),
            new StringField('NAME', [
                'required' => true,
            ]),
            new StringField('CODE', [
                'required' => true,
                'unique'   => true,
            ]),
            new StringField('REGION', [
                'required' => false,
            ]),
        ];
    }
}

3.2. Кешируем список городов

Где-нибудь в компоненте/странице:

 [
        'ID',
        'NAME',
        'CODE',
        'REGION',
    ],
    'filter' => [
        '=REGION' => 'Moscow',
    ],
    'order' => [
        'NAME' => 'ASC',
    ],
    'cache' => [
        'ttl'         => 3600, // час
        'cache_joins' => true,
    ],
]);
$cities = [];
while ($row = $result->fetch()) {
    $cities[] = $row;
}
if (!empty($cities)) {
    echo '
    '; foreach ($cities as $city) { echo '
  • '; echo htmlspecialcharsbx($city['NAME']) . ' (' . htmlspecialcharsbx($city['CODE']) . ')'; if (!empty($city['REGION'])) { echo ' — ' . htmlspecialcharsbx($city['REGION']); } echo '
  • '; } echo '
'; }

После первого выполнения запрос (SQL) будет кешироваться. При добавлении/изменении/удалении городов через CityTable::add()/update()/delete() кеш этой сущности будет очищен.

4. getByPrimary() и getRow() с кешем

Кеш работает не только с getList(), но и с другими методами ORM.

4.1. getByPrimary() с cache

 ['ID', 'NAME', 'CODE', 'REGION'],
        'cache'  => [
            'ttl' => 3600,
        ],
    ]
)->fetch();
echo '
';
print_r($city);
echo '
';

4.2. getRow() с кешем

 ['ID', 'NAME', 'CODE', 'REGION'],
    'filter' => [
        '=CODE' => 'msk',
    ],
    'cache' => [
        'ttl' => 600,
    ],
]);
echo '
';
print_r($city);
echo '
';

getRow() вернёт одну строку, но кешируется результат аналогично getList().

5. Кеш + JOIN'ы и runtime-поля (параметр cache_joins)

Параметр cache_joins управляет включением runtime-полей в формирование ключа кеша:

  • При cache_joins = false ключ формируется только по основному запросу без учёта JOIN'ов
  • При cache_joins = true в расчёт ключа попадают все runtime-поля и Reference

5.1. Описание CountryTable

Создадим таблицу стран для связи:

/local/modules/vendor.demo/lib/internals/countrytable.php:

 true,
                'autocomplete' => true,
            ]),
            new StringField('NAME', [
                'required' => true,
            ]),
            new StringField('CODE', [
                'required' => true,
                'unique'   => true,
            ]),
        ];
    }
}

5.2. Обновляем CityTable с полем COUNTRY_ID

/local/modules/vendor.demo/lib/internals/citytable.php:

 true,
                'autocomplete' => true,
            ]),
            new StringField('NAME', [
                'required' => true,
            ]),
            new StringField('CODE', [
                'required' => true,
                'unique'   => true,
            ]),
            new StringField('REGION', [
                'required' => false,
            ]),
            new IntegerField('COUNTRY_ID', [
                'required' => false,
            ]),
        ];
    }
}

5.3. Запрос с runtime-связью и кешем

 [
        'ID',
        'NAME',
        'CODE',
        'COUNTRY_ID',
        'COUNTRY_NAME' => 'COUNTRY.NAME',
        'COUNTRY_CODE' => 'COUNTRY.CODE',
    ],
    'runtime' => [
        new Reference(
            'COUNTRY',
            CountryTable::class,
            Join::on('this.COUNTRY_ID', 'ref.ID')
        ),
    ],
    'filter' => [
        '=COUNTRY.CODE' => 'ru',
    ],
    'order' => [
        'NAME' => 'ASC',
    ],
    'cache' => [
        'ttl'         => 1800,
        'cache_joins' => true, // Включаем кеш с учётом runtime-полей
    ],
]);
$cities = [];
while ($row = $result->fetch()) {
    $cities[] = $row;
}
echo '
';
print_r($cities);
echo '
';

Примечание: При использовании cache_joins = true ORM включает runtime-поля (в том числе Reference) в формирование ключа кеша. Без этого параметра запросы с разными JOIN'ами могли бы получить одинаковый кеш, что привело бы к некорректным результатам.

6. Репозиторий с ORM-кешем: один раз написал — пользуйся везде

Удобный паттерн — писать репозитории, где кеш настроен внутри, а во всём проекте просто вызываются методы.

6.1. Репозиторий для городов

/local/php_interface/lib/CityRepository.php:

 [
                'ID',
                'NAME',
                'CODE',
                'REGION',
            ],
            'order' => [
                'NAME' => 'ASC',
            ],
            'cache' => [
                'ttl' => $ttl,
            ],
        ]);
        $cities = [];
        while ($row = $result->fetch()) {
            $cities[] = $row;
        }
        return $cities;
    }
    /**
     * Получить город по коду с кешем.
     *
     * @param string $code
     * @param int    $ttl
     *
     * @return array|null
     * @throws ArgumentException
     * @throws ObjectPropertyException
     * @throws SystemException
     */
    public static function getCityByCode(string $code, int $ttl = 600): ?array
    {
        $row = CityTable::getRow([
            'select' => [
                'ID',
                'NAME',
                'CODE',
                'REGION',
            ],
            'filter' => [
                '=CODE' => $code,
            ],
            'cache' => [
                'ttl' => $ttl,
            ],
        ]);
        if (!is_array($row)) {
            return null;
        }
        return $row;
    }
}

6.2. Использование репозитория

Все города';
    echo '
';
    print_r($cities);
    echo '
'; echo '

Москва

'; echo '
';
    print_r($moscow);
    echo '
'; } catch (Exception $e) { // Обработка исключений echo 'Ошибка: ' . $e->getMessage(); }

Плюсы:

  • Весь проект обращается к городам через один слой.
  • TTL легко менять в одном месте.
  • При необходимости можно дописать ручную очистку кеша, логирование, статистику.

7. Очистка ORM-кеша: cleanCache()

ORM сам очищает кеш при add/update/delete, если вы изменяете данные через соответствующий DataManager.

Но иногда вы:

  • правите данные напрямую SQL-скриптом,
  • меняете структуру таблицы,
  • импортируете данные сторонними инструментами.

В этом случае можно принудительно сбросить кеш сущности:

Это затронет все ORM-кеши, связанные с CityTable (включая запросы с cache).

8. Как ORM-кеш сочетается с «ручным» кешем

Иногда вы можете совместить:

  • ORM-кеш (cache в getList()) — для снижения нагрузки на БД;
  • Bitrix\Main\Data\Cache или ManagedCache — для более высокого уровня кеширования (например, собрать данные из нескольких таблиц/внешних API и кешировать уже готовый массив).

Пример: кешируем итоговый список городов с ORM-кешом внутри.

initCache($ttl, $cacheId, $cacheDir)) {
    $result = $cache->getVars();
} elseif ($cache->startDataCache()) {
    // Внутри репозитория уже есть ORM-кеш
    $cities = CityRepository::getAllCities(600);
    if (empty($cities)) {
        $cache->abortDataCache();
    } else {
        $result['CITIES'] = $cities;
        // Можно добавить дополнительные теги для управления кешем
        $cache->endDataCache($result);
    }
}
echo '
';
print_r($result);
echo '
';

Таким образом:

  • На уровне ORM не будет дублирующих запросов.
  • На уровне страницы можно кешировать целиком блоки, включающие ORM-запросы.

9. Практические советы и ограничения по использованию cache в ORM

Что рекомендуется:

  1. Используйте cache для повторяющихся запросов.
    Списки сущностей, справочники (города, страны, типы, статусы), данные, которые мало меняются — идеальные кандидаты.
  2. TTL можно ставить достаточно большим.
    10–60 минут — нормальный диапазон. При изменении данных через ORM кеш всё равно сбросится.
  3. Используйте cache_joins = true при работе с runtime-полями.
    Если ваш запрос содержит runtime-поля с Reference, этот параметр обеспечит корректную работу кеша.
  4. Чистите кеш ORM после ручных SQL-операций.
    После каких-либо массовых изменений таблицы напрямую (без ORM) — вызывайте MyTable::cleanCache().
  5. Используйте репозитории для централизованного управления кешем.
    Это упрощает поддержку и позволяет быстро изменить TTL для всех запросов сущности.

Ограничения и предостережения:

  1. ORM-кеш не учитывает права доступа автоматически.
    Если данные зависят от текущего пользователя или его групп, обязательно включайте эти условия в filter или используйте другой механизм кеширования.
  2. Пагинация создает много записей кеша.
    Каждая страница (offset) создает отдельную запись в кеше. Используйте умеренные TTL для пагинированных запросов.
  3. Сложные запросы с большим количеством параметров могут создавать много уникальных ключей кеша, что увеличивает нагрузку на систему кеширования.
  4. При работе в кластере ORM-кеш работает корректно, так как использует управляемый кеш (managed_cache), который реплицируется между серверами.
  5. Не пытайтесь руками пересоздавать ключи кеша.
    ORM сам учитывает select, filter, order, runtime, limit, offset и т.д. в формировании ключа. Любое изменение параметров запроса создаст новый ключ.
  6. Мониторьте использование памяти.
    При кешировании больших результатов (тысячи записей) учитывайте потребление памяти. Используйте limit и пагинацию.

Пример настроек TTL для разных сценариев:

Тип данных Рекомендуемый TTL Примечание
Справочники (страны, города) 86400 (24 часа) Редко меняются, можно долгий TTL
Список пользователей 600 (10 минут) Могут часто добавляться
Статистические данные 300 (5 минут) Данные устаревают быстро
Контент страниц 1800 (30 минут) Зависит от частоты обновления

Заключение: ORM-кеш в Битрикс — мощный инструмент для оптимизации производительности. При правильном использовании он значительно снижает нагрузку на БД без усложнения кода. Главное — понимать его ограничения и использовать осознанно.

Теги: ORM, кеш, cache, getList, getByPrimary, getRow, D7, ORM-кеш, управляемый кеш, TTL, cache_joins, runtime-поля, Reference, DataManager, cleanCache, оптимизация запросов


Валерий Макеев
05.12.2025 09:20
Кешируем на 5 минут запрос к инфоблоку для получения 20 последних активных товаров с информацией об их категориях, используя ORM-кеш для оптимизации производительности.
Код
<?php
// Пример кешированного запроса ORM для получения списка активных товаров с привязкой к категориям

use Bitrix\Main\Loader;
use Bitrix\Iblock\ElementTable;
use Bitrix\Iblock\SectionElementTable;
use Bitrix\Iblock\SectionTable;
use Bitrix\Main\ORM\Fields\Relations\Reference;
use Bitrix\Main\ORM\Query\Join;

if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die();

Loader::includeModule('iblock');

$result = ElementTable::getList([
    'select' => [
        'ID',
        'NAME',
        'CODE',
        'ACTIVE_FROM',
        'SECTION_NAME' => 'SECTION.NAME',
        'SECTION_CODE' => 'SECTION.CODE'
    ],
    'filter' => [
        '=IBLOCK_ID' => 2,
        '=ACTIVE' => 'Y',
        '<=ACTIVE_FROM' => new \Bitrix\Main\Type\DateTime(),
        [
            'LOGIC' => 'OR',
            ['>=ACTIVE_TO' => new \Bitrix\Main\Type\DateTime()],
            ['ACTIVE_TO' => null]
        ]
    ],
    'runtime' => [
        new Reference(
            'SECTION_ELEMENT',
            SectionElementTable::class,
            Join::on('this.ID', 'ref.IBLOCK_ELEMENT_ID'),
            ['join_type' => 'INNER']
        ),
        new Reference(
            'SECTION',
            SectionTable::class,
            Join::on('this.SECTION_ELEMENT.IBLOCK_SECTION_ID', 'ref.ID'),
            ['join_type' => 'LEFT']
        )
    ],
    'order' => ['ACTIVE_FROM' => 'DESC'],
    'limit' => 20,
    'cache' => [
        'ttl' => 300,
        'cache_joins' => true
    ]
]);

$products = [];
while ($product = $result->fetch()) {
    $products[] = $product;
}

// Использование данных
foreach ($products as $product) {
    echo "{$product['NAME']} (Категория: {$product['SECTION_NAME']})<br>";
}

Стоимость услуг по разработке и сопровождению сайтов на 1C-Битрикс

Разработка корпоративного сайта

от 7 дней

от 40 000 рублей

Разработка сайта без системы оплаты заказов через корзину

* стоимость зависит от наличия верстки, использования готового решения и т.д.

Интернет-магазин на готовом решении

от 7 дней

от 40 000 рублей
запуск сайта в максимально короткие сроки

* указана минимальная стоимость. Стоимость выбранной лицензии «1С-Битрикс» оплачивается отдельно.

Участие в проекте

привлечение в проект на part-time основе

от 30 000 рублей / неделя

Возможно участие в проекте на ежедневной основе, как разработчика. Занятость - до 20 часов в неделю
Минимальный срок - одна неделя.

* сумма фиксированная