Тяжёлый getList() на справочнике городов тормозит каждый хит — типичная проблема после "cache" с ttl 3600: витрина показывает вчерашний список, хотя в админке всё поправили. Встроенный ORM-кеш в 1С-Битрикс: Управление сайтом ускоряет повторяющиеся выборки через ключ cache в getList, но требует cache_joins, cleanCache() и cache_flags. Ниже - гайд: включите кеш на staging, проверьте managed_cache и сбрасывайте выборку точечно, без "Очистить весь кеш".
Встроенный ORM-кеш доступен с main 16.5.9: передайте "cache" => ["ttl" => N] в getList, getRow или getByPrimary. Файлы лежат в /bitrix/managed_cache/MYSQL/orm_<table>/, ключ - md5(sql). После add, update, delete через DataManager кеш сбрасывается сам; после raw SQL или импорта вызовите Table::cleanCache(). JOIN без cache_joins не кешируется. Права пользователя в ключ не входят - учитывайте это в filter.
Речь про коробочную CMS, не про CRM-портал Bitrix24. ORM (Object-Relational Mapping) - слой D7, который связывает PHP-класс *Table с таблицей MySQL. Простую выборку инфоблока разобрали в сравнении GetList и ElementTable (B60). Сложные отчёты с агрегацией - в гайде по Query и ExpressionField (B83). Здесь - только встроенный кеш выборок DataManager, без обзора всех видов кеша.
Выберите, когда нужен ORM cache, а не TaggedCache или полная очистка
ORM-кеш ускоряет повторяющиеся getList() на своих Table или HL DataClass в сервисе модуля. Это не registerTag в шаблоне и не кнопка в админке.
| Механизм | Где включается | Когда использовать |
|---|---|---|
| ORM cache (cache в getList) | DataManager::getList / getRow | Тяжёлая выборка из одной ORM-талицы, одинаковая для всех посетителей |
| TaggedCache / registerTag | Компонент, result_modifier | HTML-блок витрины с тегами iblock_* - см. гайд по тегированному кешу (B66) |
| Очистка через админку | Настройки - Производительность | Аварийный сброс всего managed cache - когда и как чистить кеш (B20) |
Делайте: кешируйте ORM-выборку, если один и тот же SQL уходит на каждый хит и данные меняются через DataManager. Не делайте: не путайте CityTable::cleanCache() с clearByTag из B66 и не жмите "Очистить весь кеш" при каждой правке справочника.
Включите cache в getList и проверьте managed_cache на диске
По умолчанию кеширование выборок ORM отключено. Минимальный рабочий пример - ttl в секундах:
use Bitrix\Main\GroupTable;
$result = GroupTable::getList([
'select' => ['ID', 'NAME'],
'filter' => ['=ACTIVE' => 'Y'],
'order' => ['C_SORT' => 'ASC'],
'cache' => ['ttl' => 3600],
]);
Тот же ключ cache работает в getRow() и getByPrimary(). Второй хит читается из /bitrix/managed_cache/MYSQL/orm_b_group/, ключ файла - md5(sql). Из кеша приходит ArrayResult, не "живой" DB Result - для fetch() обычно без разницы.
- Добавьте "cache" => ["ttl" => 3600] к повторяющемуся getList на staging.
- Обновите страницу дважды и откройте /bitrix/managed_cache/MYSQL/ - появится orm_b_*.
- Сравните slow log или SqlTracker: на втором хите SQL к таблице не уходит.
- Измените запись через GroupTable::update() и зафиксируйте ttl под частоту правок: справочник раз в неделю - 86400, оперативные данные - 300-600.
Делайте: кешируйте только стабильные справочники и каталоги с редкими правками. Не делайте: не ставьте ttl 86400 на данные, которые меняются каждый час без автосброса через ORM.
Настройте cache_joins для Reference и JOIN-выборок
Выборки с JOIN и Reference по умолчанию не кешируются. Нужен cache_joins => true. Типичная ошибка на практике: ttl есть, JOIN есть, а SQL идёт каждый раз - забыли cache_joins. Или cache_joins включили, а связанное поле устарело после правки соседней таблицы.
$result = CityTable::getList([
'select' => ['ID', 'NAME', 'COUNTRY_NAME' => 'COUNTRY.NAME'],
'runtime' => [
new Reference(
'COUNTRY',
CountryTable::class,
Join::on('this.COUNTRY_ID', 'ref.ID')
),
],
'cache' => [
'ttl' => 3600,
'cache_joins' => true,
],
]);
Подробнее про Reference и Join - в гайде по связям D7 ORM (B79). Здесь важно правило: cache_joins => true только когда уверены, что связанные таблицы инвалидируются вместе или ttl приемлем.
Делайте: включайте cache_joins для стабильных JOIN-справочников. Не делайте: не кешируйте JOIN с часто меняющимися полями без короткого ttl или cleanCache() на обеих таблицах.
Сбросьте кеш через cleanCache() после raw SQL и импорта
DataManager автоматически сбрасывает ORM-кеш таблицы при add(), update(), delete(). Принудительный сброс всей выборки сущности:
CityTable::cleanCache();
// эквивалент:
CityTable::getEntity()->cleanCache();
Автосброс не сработает при прямом SQL, legacy CIBlockElement::Update, нестандартном addMulti() или импорте в обход Table. Тогда CityTable::cleanCache() один раз - свежие данные без полной очистки из B20. Workflow: импорт → cleanCache() → getList с cache → витрина обновлена.
Делайте: вешайте cleanCache() в конец скрипта импорта или агента. Не делайте: не полагайтесь на автосброс, если правка шла мимо DataManager.
Проверьте cache_flags в .settings.php, если кеш "не пишется"
DevOps может ограничить TTL таблицы глобально. В /bitrix/.settings.php секция cache → cache_flags:
'cache_flags' => [
'value' => [
'b_group_min_ttl' => 60,
'b_group_max_ttl' => 3600,
],
'readonly' => false,
],
b_<table>_min_ttl - код не задаст TTL меньше минимума. b_<table>_max_ttl - верхняя граница; значение 0 полностью запрещает ORM-кеш этой таблицы, даже если в getList указан ttl 3600. Entity::getCacheTtl() учитывает эти лимиты - приоритет у maximum.
Если в коде cache есть, а каталога orm_b_* нет - первым делом проверьте cache_flags и readonly. Для отладки "почему не кешируется" смотрите логирование через Logger (B84), а не только var_dump в шаблоне.
Делайте: согласуйте ttl в коде с лимитами на staging перед prod. Не делайте: не отлаживайте "кеш не работает" часами, если max_ttl=0 выставлен в .settings.php.
Избегите ловушек: права, offset и персональные данные
На практике в реальном проекте ORM-кеш не добавляет в ключ пользователя и группы - персональные списки кешируйте с USER_ID в filter или без ORM cache. Offset-пагинация даёт отдельный файл кеша на страницу. Кешируется Result выборки, не EO_Collection - для готового массива после fetchAll() есть отдельный слой Data\Cache.
Делайте: кешируйте публичные справочники с одинаковым результатом для всех. Не делайте: не кешируйте "мои заказы" с ttl 3600 без учёта USER_ID в filter - получите чужие данные.
Пройдите чек-лист staging: правка → свежие данные
- Включите cache с ttl на тестовом getList и убедитесь в файлах orm_* на диске.
- Измените запись через Table::update() - витрина должна обновиться без cleanCache().
- Измените ту же таблицу через raw SQL - данные "залипнут"; вызовите cleanCache().
- Проверьте JOIN: без cache_joins SQL идёт каждый раз; с cache_joins - файлы кеша и актуальность связанных полей.
- Сверьте cache_flags: при max_ttl=0 файлов orm_* не будет - это ожидаемо.
Нужна помощь с кешем и производительностью модуля - обсудим задачу, примеры внедрений - в портфолио.
Автор: Максим Мольков, Senior-разработчик 1С-Битрикс.
Источники: выборка данных ORM Bitrix Framework, кеширование ManagedCache и cache_flags, урок D7: кеширование выборки getList, урок: cache_flags в .settings.php.
Частые вопросы
Чем ORM cache отличается от registerTag в компоненте?
ORM cache живёт в параметре cache у getList/getRow и кеширует результат SQL-запроса к таблице DataManager. registerTag в компоненте помечает HTML-блок тегами iblock_* и сбрасывается через clearByTag - это слой витрины, не выборки ORM. Подробнее - в гайде B66 по тегированному кешу.
Вызывается ли cleanCache при Table::update()?
Да. add(), update() и delete() через DataManager автоматически сбрасывают ORM-кеш этой таблицы. Ручной cleanCache() нужен после raw SQL, legacy-правок в обход ORM или нестандартного массового импорта.
Зачем cache_joins при JOIN в getList?
Без cache_joins => true выборки с Reference или JOIN не кешируются, даже если указан ttl. С cache_joins ключ учитывает связанные таблицы - включайте только когда готовы к инвалидации или короткому ttl. Детали JOIN - в B79.
Где на диске лежит кеш ORM-запросов?
В /bitrix/managed_cache/MYSQL/orm_<имя_таблицы>/, например orm_b_group. Имя файла - md5 от SQL. Это управляемый кеш, не путать с /bitrix/cache/ (неуправляемый компонентный).
Почему addMulti может не сбросить кеш?
Стандартный addMulti в DataManager участвует в автосбросе, но кастомные обёртки или HL-блоки с переопределённым addMulti без вызова родителя могут обойти сброс. После массовой загрузки безопаснее вызвать Table::cleanCache() явно.
Можно ли кешировать EO_Collection из getList?
Нет. ORM cache сохраняет Result выборки как ArrayResult - набор строк. Коллекции объектов с методами и замыканиями в этот механизм не попадают. Сохраняйте массив через fetchAll() или используйте отдельный слой Data\Cache для готовых структур.