В практике разработки на платформе 1С:Предприятие 8 часто возникает ситуация, когда один сеанс записывает изменения в объект (например, в справочник или документ), а другой сеанс должен немедленно эти изменения увидеть. Однако из-за механизмов оптимизации платформы, а именно — объектного кэширования, второй сеанс может продолжать видеть старые данные в течение некоторого времени. Разберем подробно, почему это происходит и как мы можем принудительно обновить эти данные.
Проанализируем внутреннее устройство платформы. Объектный кэш предназначен для минимизации обращений к базе данных (СУБД). Когда мы обращаемся к реквизиту через точку от ссылки (например, ОбъектСсылка.Наименование), платформа сначала ищет эти данные в локальном кэше сеанса. Если данные там есть, они возвращаются мгновенно без запроса к SQL.
Существует так называемое «правило 20 секунд». Согласно документации 1С, платформа считает данные в объектном кэше «свежими» в течение 20 секунд после их первого считывания. В этот интервал времени система даже не пытается проверить версию данных (поле DataVersion) в базе данных. Это означает, что если другой пользователь изменил объект секунду назад, ваш сеанс об этом просто не узнает, пока не истечет 20-секундный таймер или пока не произойдет событие, инвалидирующее кэш.
Рассмотрим наиболее правильный с точки зрения архитектуры способ получения актуальных данных. Запросы в 1С (объект Запрос) всегда обращаются напрямую к базе данных, полностью игнорируя объектный кэш сеанса. Если нам нужно гарантированно получить значение реквизита, которое могло измениться в другом сеансе, мы должны отказаться от обращения «через точку» и написать запрос.
Посмотрим на пример кода:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Справочник1.Реквизит1 КАК АктуальноеЗначение
|ИЗ
| Справочник.Справочник1 КАК Справочник1
|ГДЕ
| Справочник1.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", НужнаяСсылка);
Результат = Запрос.Выполнить().Выбрать();
Если Результат.Следующий() Тогда
Значение = Результат.АктуальноеЗначение;
КонецЕсли;
Этот подход исключает влияние кэша и гарантирует получение данных, зафиксированных в СУБД на момент выполнения запроса. Именно поэтому в типовых конфигурациях (например, ERP или УТ 11) так часто используются функции типа ОбщегоНазначения.ЗначенияРеквизитовОбъекта(), которые внутри выполняют запрос.
Выясним причину, по которой транзакции помогают увидеть свежие данные. При открытии транзакции платформа переходит в режим повышенной согласованности. Внутри транзакции создается свой собственный «транзакционный кэш». Если в рамках транзакции мы вызываем ПолучитьОбъект(), платформа обязана проверить версию данных.
Однако, как было замечено в обсуждении, после завершения транзакции (ЗафиксироватьТранзакцию()) данные могут снова «откатиться» к старому состоянию в обычном кэше сеанса. Чтобы этого избежать и «протолкнуть» обновление в основной кэш, иногда используют следующий прием:
НачатьТранзакцию();
// Блокировка гарантирует актуальность
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Справочник.Справочник1");
ЭлементБлокировки.УстановитьПараметр("Ссылка", НужнаяСсылка);
Блокировка.Заблокировать();
// Читаем объект
Объект = НужнаяСсылка.ПолучитьОбъект();
// После фиксации данные в кэше сеанса с большой вероятностью обновятся
ЗафиксироватьТранзакцию();
Если нам критически важно обновить кэш именно для обращений через точку, и другие методы не помогают, существует «тяжелый» способ, упомянутый в сообщении №16. Это фиктивная запись объекта в текущем сеансе. При записи объекта платформа принудительно обновляет его представление в локальном кэше текущего сеанса.
Разберем, как это сделать с минимальными затратами производительности, используя флаг ОбменДанными.Загрузка:
НачатьТранзакцию();
Объект = СсылкаНаОбъект.ПолучитьОбъект();
Объект.ОбменДанными.Загрузка = Истина; // Чтобы не срабатывали проверки и подписки
Объект.Записать();
ЗафиксироватьТранзакцию();
Важно: Этот метод является крайне затратным, так как инициирует полноценную транзакцию записи в БД. Используйте его только в исключительных случаях, когда архитектура решения не позволяет перейти на использование запросов.
Проанализируем ситуацию с методом Прочитать(). Когда мы выполняем Ссылка.ПолучитьОбъект().Прочитать(), мы обновляем конкретный экземпляр объекта в памяти (в переменной). Это не обновляет глобальный объектный кэш сеанса для данной ссылки. То есть последующий вызов Ссылка.Наименование все равно может вернуть старое значение из кэша, хотя в переменной Объект.Наименование данные будут актуальными.
Поэтому, если ваша цель — обновить именно кэш ссылки, метод Прочитать() не является решением. Он полезен только для работы с конкретным экземпляром данных в коде.
Стоит учитывать, что если ваше приложение работает в кластере из нескольких рабочих процессов (rphost), то кэши у сеансов, попавших на разные процессы, абсолютно изолированы. Обновление объекта в одном rphost технически не может мгновенно «протушить» кэш в другом процессе. Платформа 1С не использует механизм распределенного кэша с общей памятью для данных объектов. Единственной точкой синхронизации остается база данных и проверка версии данных по истечении 20 секунд.
Подводя итог нашему анализу, дадим несколько практических советов:
ОповеститьОбИзменении(Ссылка). Это заставит форму перечитать данные, хотя и не гарантирует моментальную очистку серверного кэша.БлокировкаДанных). Установка блокировки заставляет платформу обратиться к СУБД для проверки текущего состояния объекта.Таким образом, штатного метода «ОчиститьКэшОбъекта(Ссылка)» в платформе не существует, так как это внутренний механизм оптимизации. Разработчик должен самостоятельно управлять актуальностью данных, выбирая между скоростью доступа (через точку) и гарантией актуальности (через запрос).