В чем разница между Движения.Записывать = Истина и Движения.Записать() в 1С?

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

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

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

Теоретическая база: Свойство "Записывать" против метода "Записать()"

Чтобы понять разницу, давайте воспользуемся простой аналогией, которая прозвучала на форуме: «Просьба» против «Команды».

Рассмотрим подробнее ключевые отличия этих механизмов, которые часто упускаются из виду.

Разница между Движения.Записать() и Движения.ИмяРегистра.Записать()

Многие разработчики путают вызов метода у всей коллекции движений документа и у конкретного регистра. Поведение платформы в этих случаях кардинально отличается:

  1. Вызов у коллекции: Движения.Записать()

    Этот метод обходит всю коллекцию регистров документа. Он записывает в базу данных только те наборы записей регистров, у которых свойство Записывать установлено в значение Истина.

    Важнейшая особенность: после выполнения метода Движения.Записать() платформа автоматически сбрасывает флаг Записывать в значение Ложь для всех успешно записанных регистров. Если после этого вызова в коде обработчика проведения вы снова измените набор записей, платформа автоматически его уже не запишет. Чтобы запись произошла в конце транзакции, вам придется вручную заново прописать Движения.ИмяРегистра.Записывать = Истина;.

  2. Вызов у конкретного набора: Движения.ИмяРегистра.Записать()

    Этот метод записывает конкретный набор записей немедленно и безусловно. Ему не важно, какое значение было у свойства Записывать.

    Важнейшая особенность: данный метод не сбрасывает маркер Записывать. Если до этого свойство было равно Истина, оно останется равным Истина. Платформа попытается повторно записать этот набор в конце транзакции, если вы не измените это свойство вручную.

Проанализируем пример кода, чтобы увидеть разницу на практике:


// Пример 1: Работа с коллекцией движений
Движения.ТоварыНаСкладах.Записывать = Истина;
// Набор записей ТоварыНаСкладах заполнен какими-то данными...

Движения.Записать(); // Записывает ТоварыНаСкладах в БД.
// После этого вызова: Движения.ТоварыНаСкладах.Записывать автоматически стало равно ЛОЖЬ!

// Пример 2: Работа с конкретным регистром
Движения.ТоварыНаСкладах.Записывать = Истина;
Движения.ТоварыНаСкладах.Записать(); // Записывает ТоварыНаСкладах в БД.
// После этого вызова: Движения.ТоварыНаСкладах.Записывать по-прежнему равно ИСТИНА!

Влияние свойства документа «Запись движений при проведении»

То, как ведет себя свойство Записывать = Истина, напрямую зависит от настроек самого объекта "Документ" в конфигураторе. На вкладке "Движения" есть важная настройка — «Запись движений при проведении». Разберем два ее режима:

  1. «Записывать выбранные» (режим по умолчанию для современных конфигураций)

    Платформа запишет движения только по тем регистрам, для которых разработчик в коде явно указал Движения.ИмяРегистра.Записывать = Истина. Даже если вы добавили новые строки в коллекцию движений, но забыли взвести этот флаг (и не вызвали метод Записать() принудительно), изменения в базе данных сохранены не будут.

  2. «Записывать модифицированные» (устаревший режим совместимости)

    Платформа сама отслеживает состояние наборов движений в памяти. Если вы добавили строки в набор или очистили его, платформа автоматически взведет флаг Записывать = Истина перед завершением транзакции проведения документа.

Зачем вообще вызывать Записать() принудительно внутри обработчика проведения?

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

Сценарий 1. Новая (современная) методика проведения и контроль остатков

При использовании старой методики проведения сначала выполнялся запрос по остаткам, принималось решение о возможности отгрузки, и только потом формировались движения. В новой методике проведения алгоритм выглядит иначе:

  1. Мы сразу формируем движения текущего документа (расход).
  2. Немедленно записываем их в базу данных с помощью метода Записать().
  3. Накладываем управляемую блокировку на анализируемые ресурсы.
  4. Выполняем запрос по остаткам. Запрос уже видит новые движения нашего документа. Если остаток ушел в минус, мы отменяем проведение (взводим Отказ = Истина).

Если в этой схеме не вызвать Записать() принудительно, запрос по остаткам просто не увидит новые движения документа, так как физически в СУБД их еще нет.

Посмотрим на пример реализации новой методики проведения:


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    // 1. Подготавливаем набор движений
    Движения.ТоварыНаСкладах.Записывать = Истина;
    Движения.ТоварыНаСкладах.Очистить();
    
    // Заполняем движения по табличной части документа
    Для Каждого СтрокаТЧ Из Товары Цикл
        Движение = Движения.ТоварыНаСкладах.ДобавитьРасход();
        Движение.Период = Дата;
        Движение.Номенклатура = СтрокаТЧ.Номенклатура;
        Движение.Склад = Склад;
        Движение.Количество = СтрокаТЧ.Количество;
    КонецЦикла;
    
    // 2. Записываем движения в базу данных ДО выполнения запроса контроля остатков
    Движения.ТоварыНаСкладах.Записать();
    
    // 3. Устанавливаем блокировку для предотвращения чтения грязных данных другими сессиями
    Блокировка = Новый БлокировкаДанных;
    ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ТоварыНаСкладах");
    ЭлементБлокировки.УстановитьЗначение("Склад", Склад);
    ЭлементБлокировки.ИсточникДанных = Товары;
    ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Номенклатура", "Номенклатура");
    Блокировка.Заблокировать();
    
    // 4. Запросом проверяем, не ушли ли остатки в минус с учетом наших движений
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
        |   ТоварыНаСкладахОстатки.КоличествоОстаток КАК Остаток
        |ИЗ
        |   РегистрНакопления.ТоварыНаСкладах.Остатки(
        |       &МоментВремени,
        |       Склад = &Склад
        |           И Номенклатура В (ВЫБРАТЬ РАЗЛИЧНЫЕ Номенклатура ИЗ &ТаблицаТоваров)) КАК ТоварыНаСкладахОстатки
        |ГДЕ
        |   ТоварыНаСкладахОстатки.КоличествоОстаток < 0";
        
    Запрос.УстановитьПараметр("МоментВремени", Новый Граница(МоментВремени(), ГраницаТипа.Включая));
    Запрос.УстановитьПараметр("Склад", Склад);
    Запрос.УстановитьПараметр("ТаблицаТоваров", Товары.ВыгрузитьКолонку("Номенклатура"));
    
    РезультатЗапроса = Запрос.Выполнить();
    Если Не РезультатЗапроса.Пустой() Тогда
        Отказ = Истина;
        Выборка = РезультатЗапроса.Выбрать();
        Пока Выборка.Следующий() Цикл
            Сообщение = Новый СообщениеПользователю;
            Сообщение.Текст = "Недостаточно товара " + Выборка.Номенклатура + " на складе. Дефицит: " + (-Выборка.Остаток);
            Сообщение.Сообщить();
        КонецЦикла;
    КонецЕсли;

КонецПроцедуры

Сценарий 2. Предотвращение взаимоблокировок (Deadlocks)

Если документ делает движения по нескольким крупным регистрам, СУБД накладывает блокировки на таблицы СУБД. При параллельной работе множества пользователей и одновременном проведении документов разного вида может возникнуть ситуация взаимной блокировки (Deadlock). Это происходит, когда один сеанс заблокировал Таблицу А и ждет Таблицу Б, а второй сеанс заблокировал Таблицу Б и ждет Таблицу А.

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


// Во всех документах системы запись регистров должна происходить в одинаковом порядке
Движения.ТоварыНаСкладах.Записать();
Движения.Продажи.Записать();
Движения.Взаиморасчеты.Записать();

СУБД будет блокировать ресурсы в одном и том же порядке для всех транзакций, что сводит вероятность дедлоков практически к нулю.

Сценарий 3. Быстрая очистка движений (запись пустого набора)

Иногда требуется очистить движения документа по определенному регистру прямо в процессе работы алгоритма. Самый быстрый способ сделать это — очистить набор записей в памяти и сразу же принудительно записать его в базу данных:


Движения.ТоварыНаСкладах.Очистить();
Движения.ТоварыНаСкладах.Записать(); // База данных мгновенно стирает старые движения этого документа

Параметр «Замещать» в методе Записать()

Важным преимуществом метода Записать() является возможность управления замещением записей в базе данных. Метод имеет следующий синтаксис:

Записать(<Замещать>)

Параметр Замещать принимает значение типа Булево. По умолчанию он равен Истина, что означает: старые движения этого документа по регистру полностью стираются и заменяются новыми записями, которые находятся в наборе.

Если же передать значение Ложь:


// Новые движения будут добавлены к уже существующим движениям документа в БД
Движения.ТоварыНаСкладах.Записать(Ложь);

С помощью свойства Записывать = Истина управлять этим процессом невозможно. Платформа при автоматической записи в конце транзакции всегда производит запись с замещением.

Резюме

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

← На главную