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

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
Что рекомендуется:
- Используйте
cacheдля повторяющихся запросов.
Списки сущностей, справочники (города, страны, типы, статусы), данные, которые мало меняются — идеальные кандидаты. - TTL можно ставить достаточно большим.
10–60 минут — нормальный диапазон. При изменении данных через ORM кеш всё равно сбросится. - Используйте
cache_joins = trueпри работе с runtime-полями.
Если ваш запрос содержитruntime-поля сReference, этот параметр обеспечит корректную работу кеша. - Чистите кеш ORM после ручных SQL-операций.
После каких-либо массовых изменений таблицы напрямую (без ORM) — вызывайтеMyTable::cleanCache(). - Используйте репозитории для централизованного управления кешем.
Это упрощает поддержку и позволяет быстро изменить TTL для всех запросов сущности.
Ограничения и предостережения:
- ORM-кеш не учитывает права доступа автоматически.
Если данные зависят от текущего пользователя или его групп, обязательно включайте эти условия вfilterили используйте другой механизм кеширования. - Пагинация создает много записей кеша.
Каждая страница (offset) создает отдельную запись в кеше. Используйте умеренные TTL для пагинированных запросов. - Сложные запросы с большим количеством параметров могут создавать много уникальных ключей кеша, что увеличивает нагрузку на систему кеширования.
- При работе в кластере ORM-кеш работает корректно, так как использует управляемый кеш (managed_cache), который реплицируется между серверами.
- Не пытайтесь руками пересоздавать ключи кеша.
ORM сам учитываетselect,filter,order,runtime,limit,offsetи т.д. в формировании ключа. Любое изменение параметров запроса создаст новый ключ. - Мониторьте использование памяти.
При кешировании больших результатов (тысячи записей) учитывайте потребление памяти. Используйтеlimitи пагинацию.
Пример настроек TTL для разных сценариев:
| Тип данных | Рекомендуемый TTL | Примечание |
|---|---|---|
| Справочники (страны, города) | 86400 (24 часа) | Редко меняются, можно долгий TTL |
| Список пользователей | 600 (10 минут) | Могут часто добавляться |
| Статистические данные | 300 (5 минут) | Данные устаревают быстро |
| Контент страниц | 1800 (30 минут) | Зависит от частоты обновления |
Заключение: ORM-кеш в Битрикс — мощный инструмент для оптимизации производительности. При правильном использовании он значительно снижает нагрузку на БД без усложнения кода. Главное — понимать его ограничения и использовать осознанно.