Почему запрос в цикле не видит только что созданные записи в регистре сведений?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В практике разработки на платформе 1С:Предприятие 8 часто встречается ситуация, когда в рамках одного цикла нам необходимо записывать данные в регистр сведений и тут же проверять их наличие с помощью запроса. Казалось бы, логика проста: записали — проверили — увидели. Однако на деле программисты сталкиваются с тем, что запрос упорно возвращает пустой результат, хотя отладка подтверждает успешное выполнение метода Записать(). Разберем подробно, почему это происходит и как правильно организовать работу с данными в подобных сценариях.

Анализ проблемы: почему данные «исчезают»?

Рассмотрим ситуацию, с которой столкнулся автор темы. В цикле происходит обработка файлов. Перед записью информации о файле в регистр АрхивTS выполняется запрос, чтобы убедиться, что такого файла еще нет в базе. Если запрос ничего не находит, создается менеджер записи, заполняются ресурсы и вызывается метод Записать(). На следующей итерации цикла, даже если файл с таким же именем снова попадает в обработку, запрос его «не видит».

Давайте проанализируем основные причины такого поведения системы:

  1. Кэширование результатов запросов (Query Result Caching): В современных версиях платформы (8.3.15+) реализованы механизмы оптимизации. Если запрос с абсолютно одинаковыми параметрами выполняется многократно в короткий промежуток времени внутри одного сеанса, платформа может вернуть результат из своего внутреннего кэша, не обращаясь к СУБД. В итоге запрос видит состояние базы на момент первого обращения.
  2. Уровни изоляции транзакций: Несмотря на отсутствие явного вызова НачатьТранзакцию(), любая операция записи порождает неявную транзакцию. Если база работает в режиме Snapshot Isolation (версионное хранилище), текущий сеанс может видеть «срез» данных, актуальный на момент начала какой-либо внутренней операции, и не замечать изменений до завершения определенного процесса.
  3. Задержки обновления индексов на уровне СУБД: При использовании индексированных ресурсов (как в примере автора) запись в таблицу происходит мгновенно, но обновление индекса в СУБД может иметь микроскопическую задержку. Запрос, использующий индекс для поиска, может «промахнуться», если обращение происходит в ту же миллисекунду.
  4. Использование Менеджера записи: Объект РегистрСведенийМенеджерЗаписи является довольно «тяжелым» и работает медленнее, чем наборы записей, так как выполняет целый ряд проверок и блокировок при каждой итерации.

Решение №1: Использование локального кэша в Таблице Значений

Это самый эффективный и производительный способ, к которому в итоге пришел автор вопроса. Вместо того чтобы на каждой итерации «мучить» базу данных и СУБД запросами, мы создаем структуру данных в оперативной памяти (коллекцию), которая будет гарантированно содержать все новые записи.

Рассмотрим алгоритм реализации:

  1. Перед началом цикла выполняем один общий запрос, который выгружает уже существующие в базе имена файлов в ТаблицаЗначений.
  2. Для таблицы значений строим индекс по ключевому полю для ускорения поиска методом Найти().
  3. Внутри цикла сначала ищем имя файла в этой таблице.
  4. Если не нашли — записываем в регистр и обязательно добавляем новую строку в нашу таблицу значений.

Посмотрим на пример кода:


// 1. Подготовка данных перед циклом
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	АрхивTS.ИмяTSФайлаСРасширением КАК ИмяФайла
    |ИЗ
    |	РегистрСведений.АрхивTS КАК АрхивTS";

ТаблицаКэш = Запрос.Выполнить().Выгрузить();
ТаблицаКэш.Индексы.Добавить("ИмяФайла");

// 2. Основной цикл обработки
Для Каждого ТекущийФайл Из СписокФайлов Цикл
    
    // Ищем в локальном кэше (это работает мгновенно и всегда видит новые данные)
    ПараметрыПоиска = Новый Структура("ИмяФайла", ТекущийФайл.Имя);
    НайденныеСтроки = ТаблицаКэш.НайтиСтроки(ПараметрыПоиска);
    
    Если НайденныеСтроки.Количество() > 0 Тогда
        Продолжить; // Файл уже обрабатывался
    КонецЕсли;
    
    // 3. Запись в регистр
    МенеджерЗаписи = РегистрыСведений.АрхивTS.СоздатьМенеджерЗаписи();
    МенеджерЗаписи.ИмяTSФайлаСРасширением = ТекущийФайл.Имя;
    МенеджерЗаписи.Период = ТекущаяДата();
    МенеджерЗаписи.Записать();
    
    // 4. Обновление локального кэша
    НоваяСтрока = ТаблицаКэш.Добавить();
    НоваяСтрока.ИмяФайла = ТекущийФайл.Имя;
    
КонецЦикла;

Решение №2: Обход кэширования через «ДЛЯ ИЗМЕНЕНИЯ»

Если по каким-то причинам использование таблицы значений невозможно (например, объем данных слишком велик для оперативной памяти), мы можем заставить платформу 1С и СУБД игнорировать кэш и читать самые актуальные данные прямо из таблиц базы данных. Для этого в тексте запроса используется конструкция ДЛЯ ИЗМЕНЕНИЯ.

Проанализируем ситуацию: когда мы добавляем это ключевое слово, система устанавливает исключительную блокировку на читаемые записи (или диапазон). Это гарантирует, что запрос обратится к реальным данным СУБД, минуя любые оптимизационные прослойки кэша сеанса.

Пример изменения текста запроса:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	АрхивTS.ИмяTSФайлаСРасширением КАК ИмяTSФайлаСРасширением
    |ИЗ
    |	РегистрСведений.АрхивTS КАК АрхивTS
    |ГДЕ
    |	АрхивTS.ИмяTSФайлаСРасширением = &ИмяTSФайлаСРасширением
    |ДЛЯ ИЗМЕНЕНИЯ"; // Ключевое слово для актуализации данных

Запрос.УстановитьПараметр("ИмяTSФайлаСРасширением", ИмяТекущегоФайла);
Результат = Запрос.Выполнить();

Важно: Использование ДЛЯ ИЗМЕНЕНИЯ значительно повышает нагрузку на СУБД и может приводить к блокировкам, если несколько пользователей одновременно выполняют подобный код. Используйте этот метод с осторожностью.

Решение №3: Переход на Наборы Записей

Объект РегистрыСведенийНаборЗаписей работает более прямолинейно по сравнению с МенеджерЗаписи. При записи набора платформа выполняет более «жесткую» фиксацию изменений. Если ваша задача позволяет, попробуйте заменить создание менеджера записи на работу с набором из одной записи.

Рассмотрим пример:


Набор = РегистрыСведений.АрхивTS.СоздатьНаборЗаписей();
Набор.Отбор.Период.Установить(ТекущийПериод);
Набор.Отбор.ИмяTSФайлаСРасширением.Установить(ИмяTS_ФайлаУНас);

НоваяЗапись = Набор.Добавить();
НоваяЗапись.Период = ТекущийПериод;
НоваяЗапись.ИмяTSФайлаСРасширением = ИмяTS_ФайлаУНас;

// Запись набора часто форсирует обновление состояния данных для текущего контекста
Набор.Записать();

Дополнительные рекомендации по отладке

Если проблема сохраняется, выполним проверку следующих моментов:

Подводя итог, можно сказать, что лучшей практикой является вынос запроса за пределы цикла и использование ТаблицыЗначений для накопления результатов в памяти. Это не только решает проблему «невидимости» данных, но и ускоряет работу программы в десятки раз, минимизируя количество обращений к серверу базы данных.

← На главную