Как правильно записывать движения в регистры при проведении документа в 1С?

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

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

Способ 1. Автоматическая запись движений платформой при выходе из обработчика

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


Движения.ОстаткиНоменклатуры.Записывать = Истина;
Для Каждого Стр Из ТаблицаТоваров Цикл
    Движение = Движения.ОстаткиНоменклатуры.Добавить();
    Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
    Движение.Период = Дата;
    Движение.Номенклатура = Стр.Номенклатура;
    Движение.Количество = Стр.Количество;
КонецЦикла;

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

Поведение платформы при таком подходе регулируется свойством документа в дереве метаданных «Запись движений при проведении»:

  1. Записывать выбранные: платформа запишет только те наборы записей, у которых свойство Записывать было установлено в Истина. Это наиболее контролируемый вариант.
  2. Записывать модифицированные: платформа анализирует изменения в наборах записей и автоматически записывает те наборы, состав строк которых изменился в процессе выполнения обработчика.
  3. Не записывать автоматически: автоматическая запись отключается полностью, и разработчик обязан явно вызывать методы записи.

Способ 2. Вызов метода Движения.Записать() для всей коллекции

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

Рассмотрим пример использования при новой методике проведения, когда перед проверкой остатков требуется сначала зафиксировать расход в базе данных:


Движения.ОстаткиНоменклатуры.Записывать = Истина;
// Заполняем набор записей
Для Каждого Стр Из СписокТоваров Цикл
    Движение = Движения.ОстаткиНоменклатуры.ДобавитьРасход();
    Движение.Период = Дата;
    Движение.Номенклатура = Стр.Номенклатура;
    Движение.Количество = Стр.Количество;
КонецЦикла;

// Записываем сформированные движения в БД
Движения.Записать();

// Далее выполняем запрос контроля отрицательных остатков

Преимущества метода Движения.Записать():

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

Способ 3. Явная запись отдельного набора через Движения.ИмяРегистра.Записать()

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


// Избыточная конструкция:
Движения.ОстаткиНоменклатуры.Записывать = Истина;
// ... заполнение движений ...
Движения.ОстаткиНоменклатуры.Записать();

Если вы вызываете Записать() напрямую у набора, строчка Движения.ОстаткиНоменклатуры.Записывать = Истина теряет смысл. Более того, прямой вызов метода Записать() у отдельного набора не сбрасывает системный признак, и если в метаданных установлено «Записывать выбранные», важно следить за тем, чтобы не спровоцировать дублирование операций записи.

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

Оптимизация заполнения через Загрузить()

Вместо построчного обхода табличной части и добавления строк через Добавить() эффективнее сформировать результат запросом и загрузить его напрямую в набор записей. Посмотрим на пример:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	&Период КАК Период,
    |	ЗНАЧЕНИЕ(ВидДвиженияНакопления.Расход) КАК ВидДвижения,
    |	Док.Номенклатура КАК Номенклатура,
    |	Док.Склад КАК Склад,
    |	СУММА(Док.Количество) КАК Количество
    |ИЗ
    |	Документ.РеализацияТоваровУслуг.Товары КАК Док
    |ГДЕ
    |	Док.Ссылка = &Ссылка
    |СГРУППИРОВАТЬ ПО
    |	Док.Номенклатура,
    |	Док.Склад";

Запрос.УстановитьПараметр("Период", Дата);
Запрос.УстановитьПараметр("Ссылка", Ссылка);

ТаблицаДвижений = Запрос.Выполнить().Выгрузить();

Движения.ОстаткиНоменклатуры.Записывать = Истина;
Движения.ОстаткиНоменклатуры.Загрузить(ТаблицаДвижений);

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

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

При проведении документов в многопользовательской базе необходимо учитывать следующие правила работы СУБД и транзакций:

  1. Таблицы итогов: даже если документы имеют разные ссылки (регистраторы), они могут конфликтовать на уровне записей таблиц итогов регистра, если проводятся по одинаковым измерениям (например, по одному и тому же складу и номенклатуре).
  2. Длительность блокировок: блокировки, наложенные СУБД при вызове метода Записать(), удерживаются до самого конца транзакции (до выхода из процедуры проведения или вызова ЗафиксироватьТранзакцию). Поэтому не следует вызывать запись в самом начале длительных расчетов.
  3. Исключение DeadLock: если документ делает движения по нескольким регистрам (например, ОстаткиНоменклатуры и Продажи), все параллельно работающие документы должны блокировать и записывать эти таблицы в одинаковом порядке. Использование метода Движения.Записать() гарантирует соблюдение единого порядка записи по метаданным.
← На главную