В процессе разработки на платформе «1С:Предприятие 8» одним из наиболее важных и при этом часто понимаемых поверхностно вопросов является механизм работы с движениями документов. Часто возникает вопрос: что происходит на уровне базы данных, когда мы перепроводим документ, в котором изменилось количество строк в табличной части? Удалятся ли старые записи полностью или система попытается обновить их выборочно? Разберем эту тему подробно, опираясь на логику работы платформы и механизмы СУБД.
Для начала проанализируем настройки самого документа в конфигураторе. Свойство Удаление движений может принимать несколько значений, и именно от них зависит поведение системы в момент нажатия кнопки «Провести».
ОбработкаПроведения. Это гарантирует, что в момент расчета остатков внутри кода старые записи этого же документа не будут «мешаться». Однако это создает двойную нагрузку на СУБД.Записать(Истина), произойдет замещение.Рассмотрим ситуацию, которую описывает автор вопроса: в старом документе было 10 движений, мы удалили 5 строк из табличной части и нажали «Провести». Каким образом «перезапишется» только часть движений?
Выясним причину: на самом деле термин «перезапись» в контексте 1С не означает частичное обновление строк (SQL UPDATE). Платформа работает с наборами записей. Набор записей по конкретному регистратору — это всегда целостная сущность. Когда выполняется метод НаборЗаписей.Записать(Истина), платформа подает СУБД команду на полное удаление всех существующих строк по данному регистратору в таблице регистра и последующую вставку новых строк.
Таким образом, даже если новых строк стало меньше (или они вовсе отсутствуют), старые данные будут полностью стерты. В базе данных не останется «лишних» записей от предыдущего проведения, так как операция удаления по фильтру Регистратор всегда предшествует операции вставки.
Посмотрим на ситуацию глазами SQL-сервера. Когда мы выполняем проведение документа в режиме замещения движений, внутри одной транзакции происходят следующие действия:
DELETE FROM [ТаблицаРегистра] WHERE [Recorder] = 'УникальныйИдентификаторДокумента'.INSERT INTO для каждой новой строки.Это объясняет, почему количество записей может быть любым. Если программист передал пустой набор записей и вызвал Записать(), то выполнится только DELETE, и движения просто исчезнут, что и требуется при логическом удалении данных.
Проанализируем, почему в старых конфигурациях (на обычных формах) так часто использовали режим Удалять автоматически. Это связано с проблемой чтения остатков «на самих себе».
Представим сценарий: на складе 10 единиц товара. Документ «Расходная накладная» уже проведен и списал 5 единиц. В регистре числится остаток 5. Мы решили перепровести этот документ, изменив количество с 5 на 7.
Если мы используем режим Удалять автоматически при отмене проведения, то внутри процедуры ОбработкаПроведения запрос к остаткам вернет нам 5 единиц (так как старые движения документа еще не удалены). Логика контроля остатков может выдать ошибку: «Недостаточно товара, нужно 7, в наличии 5». Хотя на самом деле товара достаточно (10 единиц).
Чтобы избежать этой ситуации в современном коде, мы используем следующие подходы:
Движения.Запасы.Записывать = Истина;
Движения.Запасы.Очистить();
Движения.Записать();
УстановитьГраницу() в запросах, чтобы исключить движения текущего документа из расчета.Выбор правильного режима удаления движений напрямую влияет на скорость работы системы и вероятность возникновения дедлоков (Deadlocks). Рассмотрим подробнее:
При режиме Удалять автоматически (Always) платформа делает лишнюю работу: она блокирует таблицы и удаляет данные еще до того, как основной код проведения начнет выполняться. Если проведение документа по какой-то причине прервется (например, сработает отказ из-за нехватки прав или ошибки в коде), СУБД придется делать откат (Rollback) операции удаления.
В высоконагруженных системах рекомендуется отключать автоматическое удаление движений. Программист сам управляет моментом записи. Это позволяет объединить удаление старых и запись новых данных в одну эффективную операцию на уровне СУБД, минимизируя время блокировки таблиц.
Разберем по шагам оптимальную схему работы с движениями в новом документе:
Удаление движений в значение Удалять автоматически при отмене проведения.ОбработкаПроведения установите свойство Записывать для нужных регистров в Истина.Пример кода для эффективной перезаписи:
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
// Помечаем, что набор записей нужно будет записать
Движения.ОстаткиТоваров.Записывать = Истина;
// Если нужно очистить старое ПЕРЕД расчетами:
Движения.ОстаткиТоваров.Записать();
// Формируем новые движения на основе табличной части
Для Каждого СтрокаТЧ Из Товары Цикл
Движение = Движения.ОстаткиТоваров.ДобавитьРасход();
Движение.Период = Дата;
Движение.Номенклатура = СтрокаТЧ.Номенклатура;
Движение.Количество = СтрокаТЧ.Количество;
КонецЦикла;
// В конце процедуры платформа сама вызовет Записать() для всех наборов,
// у которых Записывать = Истина, используя режим замещения.
КонецПроцедуры
Таким образом, мы выяснили, что система 1С работает очень надежно в плане целостности данных: при любом изменении состава документа «лишние» записи в регистрах не остаются, так как механизм замещения набора записей гарантирует полное обновление данных по регистратору в базе данных.