Нужно ли вызывать Записать() для набора записей регистра накопления, чтобы прочитать их запросом в транзакции?

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

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

Оперативная память против Базы Данных

Для начала проанализируем, где физически находятся данные в разные моменты выполнения кода. Когда мы обращаемся к свойству Движения.ИмяРегистра, платформа предоставляет нам объект типа РегистрНакопленияНаборЗаписей. Это программная оболочка, которая существует исключительно в оперативной памяти сервера приложений.

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

Рассмотрим ситуацию на примере:


// Наполнение набора в памяти
НоваяЗапись = Движения.ОстаткиТоваров.Добавить();
НоваяЗапись.Период = Дата;
НоваяЗапись.Количество = 10;
// В этот момент в БД записей еще нет

// Если мы выполним запрос к таблице ОстаткиТоваров сейчас, 
// результат будет пустым (или покажет старые данные).

Методика «Запись — Чтение» для контроля остатков

Выясним причину, по которой во многих типовых решениях (особенно на обычных формах или в исторических механизмах) используется принудительная запись движений. Это стандартный паттерн для реализации контроля отрицательных остатков. Разберем этот алгоритм по шагам:

  1. Очистка старых движений: Перед началом формирования новых записей необходимо убедиться, что старые движения этого документа (если он перепроводится) не мешают расчету.
  2. Заполнение набора: Программное формирование новых строк в коллекции Движения.
  3. Принудительная запись: Вызов Движения.ИмяРегистра.Записать(). В этот момент данные улетают в СУБД. Важно: они попадают в базу внутри текущей транзакции. Другие пользователи их еще не видят, но наш текущий сеанс уже может работать с ними через запросы.
  4. Проверка остатков: Выполняется запрос к виртуальной таблице Остатки. Поскольку мы вызвали Записать(), виртуальная таблица пересчитывается с учетом наших новых движений.
  5. Отказ от проведения: Если в результате запроса обнаружены «минусы», мы устанавливаем Отказ = Истина. Транзакция откатывается, и записи удаляются из БД автоматически.

Без вызова Записать() в этой схеме запрос к остаткам просто не учтет то, что документ пытается списать сейчас.

Влияние свойств документа на автоматическую запись

Посмотрим на настройки метаданных документа. На вкладке «Движения» есть важное свойство «Записывать выбранные» (или настройки модифицированности в коде). Рассмотрим, как они влияют на поведение платформы:

Если установлено значение «Записывать модифицированные», платформа сама вызовет Записать() для всех измененных наборов записей сразу после завершения процедуры ОбработкаПроведения, но до завершения транзакции. Однако, если вам нужно прочитать эти данные внутри процедуры, автоматики ждать нельзя — нужно вызывать метод вручную.

Важный нюанс: Если вы вызвали Набор.Записать() вручную, платформа помечает этот набор как «немодифицированный». Это означает, что в конце процедуры повторная автоматическая запись не произойдет, если вы снова не внесете изменения в набор.

Блокировки данных и производительность

Проанализируем ситуацию с точки зрения производительности. Вызов Записать() — это ресурсоемкая операция. Она инициирует установку исключительных блокировок в СУБД. Если вы вызываете Записать() несколько раз для одного и того же регистра внутри одной процедуры (например, в цикле), это значительно повышает риск возникновения дедлоков (Deadlocks) и замедляет работу системы.

Рекомендуется придерживаться правила: один набор записей — одна запись в базу за всю транзакцию. Если вам нужно выполнить проверку остатков, постарайтесь сначала подготовить все движения по всем нужным регистрам, один раз вызвать Записать() для каждого, и только потом выполнять один общий запрос на проверку всех остатков.

Современный подход: использование временных таблиц

В современных высоконагруженных системах (например, в последних версиях ERP или УТ 11) от паттерна «Запись — Чтение» часто отказываются в пользу более производительного метода. Давайте его кратко рассмотрим:

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

Это позволяет:

Резюме

Подведем итог: если ваша задача состоит в том, чтобы прочитать движения текущего документа через объект Запрос прямо в процессе проведения, вам обязательно нужно вызвать НаборЗаписей.Записать(). Если же вы просто формируете движения и вам не нужно проверять их влияние на остатки в этой же процедуре, вызывать Записать() не обязательно — платформа сделает это за вас при завершении транзакции (при соответствующих настройках метаданных).

Пример правильной последовательности для классического метода:


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

Помните, что использование Записать() без параметров эквивалентно Записать(Истина), что означает полное замещение существующих записей по данному регистратору. Это стандартное и ожидаемое поведение при проведении документов.

← На главную