Почему ПланыОбмена.УдалитьРегистрациюИзменений удаляет лишние изменения и как это исправить?

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

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

Анализ исходной проблемы

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


Выборка = ПланыОбмена.ВыбратьИзменения(Узел, НомерСообщения);
Сч = 0;
Пока Выборка.Следующий() Цикл
    Сч = Сч + 1;
    Сообщить(Выборка.Получить());
КонецЦикла; 
Сообщить("Всего: "+Сч);

// Попытка удалить старые изменения:
ПланыОбмена.УдалитьРегистрациюИзменений(Узел, НомерСообщения - 1);

Если мы передадим в этот код НомерСообщения = 9, а затем вызовем удаление для номера 8, мы можем с удивлением обнаружить, что при следующей выборке для номера 9 изменений больше нет. Кажется, что метод удалил изменения, которые мы только что выбрали для нового сообщения. Но на самом деле причина кроется в скрытой логике присвоения номеров.

Как работает метод ВыбратьИзменения()

Рассмотрим по шагам, что происходит под капотом, когда мы вызываем метод ПланыОбмена.ВыбратьИзменения:

  1. Платформа обращается к таблицам регистрации изменений для указанного узла.
  2. Система проверяет поле номера сообщения у зарегистрированного изменения.
  3. Если номер сообщения равен NULL (то есть объект был изменен недавно и еще ни разу не выгружался), платформа присваивает ему тот номер, который мы передали в метод.
  4. Критический момент: Если изменение уже попадало в выборку ранее (например, при формировании пакета №8), то за ним уже закреплен номер 8. При вызове метода для номера 9 платформа не перезапишет номер 8 на 9. Объект так и останется в базе с привязкой к 8-му сообщению.

Именно поэтому, когда мы в коде вызываем ПланыОбмена.УдалитьРегистрациюИзменений(Узел, 8), система честно отрабатывает свое назначение: она удаляет из таблиц регистрации все записи, у которых номер сообщения меньше или равен переданному параметру (в нашем случае <= 8). Поскольку наши объекты так и остались с номером 8, они благополучно стираются, несмотря на то, что мы пытались прочитать их под 9-м номером.

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

Проанализируем ситуацию с архитектурной точки зрения. Пытаться очищать таблицы регистрации одновременно с генерацией нового пакета без подтверждения от узла-приемника — в корне неверный подход. Это нарушает принцип гарантированной доставки сообщений.

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

Стандартный и надежный сценарий (квитирование) выглядит так:

  1. Чтение квитанции: При загрузке входящего файла из другой базы мы считываем номер успешно загруженного ими сообщения. Для этого используется свойство ЧтениеСообщения.НомерПринятого.
  2. Удаление подтвержденного: Только теперь мы можем безопасно удалить регистрацию тех данных, в загрузке которых мы уверены. Вызываем метод ПланыОбмена.УдалитьРегистрациюИзменений(Узел, НомерПринятого).
  3. Формирование нового пакета: Генерируем новый номер для исходящего сообщения (он должен быть больше предыдущего) и вызываем ПланыОбмена.ВыбратьИзменения. В этот пакет попадут объекты с невыгруженными изменениями (NULL), а также те старые изменения (если они были), которые не вошли в номер из квитанции (т.е. приемник их еще не подтвердил).

Блокировки и оптимизация в высоконагруженных системах

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

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

← На главную