Блог разработчика 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():

<?php
use Bitrix\Main\Loader;
use Bitrix\Main\UserTable;
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) {
    die();
}
if (!Loader::includeModule('main')) {
    ShowError('Модуль main не найден');
    return;
}
$result = UserTable::getList([
    'select' => [
        '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 '<pre>';
print_r($users);
echo '</pre>';

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

  • Ключ кеша считается из всех параметров запроса (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:

<?php
namespace Vendor\Demo\Internals;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields\IntegerField;
use Bitrix\Main\ORM\Fields\StringField;
class CityTable extends DataManager
{
    public static function getTableName()
    {
        // Имя таблицы в БД
        return 'b_demo_city';
    }
    public static function getMap()
    {
        return [
            new IntegerField('ID', [
                'primary'      => true,
                'autocomplete' => true,
            ]),
            new StringField('NAME', [
                'required' => true,
            ]),
            new StringField('CODE', [
                'required' => true,
                'unique'   => true,
            ]),
            new StringField('REGION', [
                'required' => false,
            ]),
        ];
    }
}

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

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

<?php
use Bitrix\Main\Loader;
use Vendor\Demo\Internals\CityTable;
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) {
    die();
}
// Подключаем модуль, если CityTable лежит в модуле vendor.demo
if (!Loader::includeModule('vendor.demo')) {
    ShowError('Модуль vendor.demo не установлен');
    return;
}
$result = CityTable::getList([
    'select' => [
        '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 '<ul>';
    foreach ($cities as $city) {
        echo '<li>';
        echo htmlspecialcharsbx($city['NAME']) . ' (' . htmlspecialcharsbx($city['CODE']) . ')';
        if (!empty($city['REGION'])) {
            echo ' — ' . htmlspecialcharsbx($city['REGION']);
        }
        echo '</li>';
    }
    echo '</ul>';
}

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

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

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

4.1. getByPrimary() с cache

<?php
use Vendor\Demo\Internals\CityTable;
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) {
    die();
}
$cityId = 10;
$city = CityTable::getByPrimary(
    $cityId,
    [
        'select' => ['ID', 'NAME', 'CODE', 'REGION'],
        'cache'  => [
            'ttl' => 3600,
        ],
    ]
)->fetch();
echo '<pre>';
print_r($city);
echo '</pre>';

4.2. getRow() с кешем

<?php
use Vendor\Demo\Internals\CityTable;
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) {
    die();
}
$city = CityTable::getRow([
    'select' => ['ID', 'NAME', 'CODE', 'REGION'],
    'filter' => [
        '=CODE' => 'msk',
    ],
    'cache' => [
        'ttl' => 600,
    ],
]);
echo '<pre>';
print_r($city);
echo '</pre>';

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:

<?php
namespace Vendor\Demo\Internals;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields\IntegerField;
use Bitrix\Main\ORM\Fields\StringField;
class CountryTable extends DataManager
{
    public static function getTableName()
    {
        return 'b_demo_country';
    }
    public static function getMap()
    {
        return [
            new IntegerField('ID', [
                'primary'      => 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:

<?php
namespace Vendor\Demo\Internals;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields\IntegerField;
use Bitrix\Main\ORM\Fields\StringField;
class CityTable extends DataManager
{
    public static function getTableName()
    {
        return 'b_demo_city';
    }
    public static function getMap()
    {
        return [
            new IntegerField('ID', [
                'primary'      => 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-связью и кешем

<?php
use Bitrix\Main\ORM\Fields\Relations\Reference;
use Bitrix\Main\ORM\Query\Join;
use Vendor\Demo\Internals\CityTable;
use Vendor\Demo\Internals\CountryTable;
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) {
    die();
}
$result = CityTable::getList([
    'select' => [
        '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 '<pre>';
print_r($cities);
echo '</pre>';

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

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

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

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

/local/php_interface/lib/CityRepository.php:

<?php
namespace Local;
use Bitrix\Main\ArgumentException;
use Bitrix\Main\ObjectPropertyException;
use Bitrix\Main\SystemException;
use Vendor\Demo\Internals\CityTable;
class CityRepository
{
    /**
     * Получить список всех активных городов с кешем.
     *
     * @param int $ttl Время кеша в секундах
     *
     * @return array
     * @throws ArgumentException
     * @throws ObjectPropertyException
     * @throws SystemException
     */
    public static function getAllCities(int $ttl = 3600): array
    {
        $result = CityTable::getList([
            'select' => [
                '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. Использование репозитория

<?php
use Local\CityRepository;
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) {
    die();
}
try {
    // Получаем все города (кеш 1 час)
    $cities = CityRepository::getAllCities();

    // Получаем конкретный (кеш 10 минут)
    $moscow = CityRepository::getCityByCode('msk');

    echo '<h3>Все города</h3>';
    echo '<pre>';
    print_r($cities);
    echo '</pre>';

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

Плюсы:

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

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

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

Но иногда вы:

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

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

<?php
use Vendor\Demo\Internals\CityTable;
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) {
    die();
}
// Полностью очистить кеш по сущности CityTable
CityTable::cleanCache();

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

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

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

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

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

<?php
use Bitrix\Main\Data\Cache;
use Local\CityRepository;
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) {
    die();
}
$ttl      = 3600;
$cacheId  = 'page_cities_block_v1';
$cacheDir = '/demo/page_cities';
// Используем стандартный механизм кеширования
$cache = Cache::createInstance();
$result = [];
if ($cache->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 '<pre>';
print_r($result);
echo '</pre>';

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

  • На уровне 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 рублей
запуск сайта в максимально короткие сроки

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

Техническая поддержка

сайтов на CMS 1C-Битрикс

от 20 000 рублей/месяц
Оптимизация производительности действующих интернет-проектов, наполнение и сопровождение, полная техническая поддержка и продвижение в поисковых сетях.

* стоимость зависит от объема и сложности выполняемых работ

Лендинг

от 3 дней

от 25 000 рублей

Разработка одностраничного сайта на платформе Битрикс

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