При разработке собственных механизмов обмена данными в 1С:Предприятие 8 часто возникает ситуация, когда программное удаление объектов приводит к появлению «фантомных» записей или «битых ссылок» в узлах плана обмена. Эти записи выглядят как <Объект не найден> или содержат внутренний идентификатор (GUID) в скобках. Разберем подробно, почему так происходит, и как правильно управлять очередью обмена, чтобы поддерживать базу в чистоте.
Рассмотрим ситуацию, с которой столкнулся автор темы. В его коде процедура сначала физически удаляет объект из базы данных, а затем пытается отменить его регистрацию в плане обмена. Проанализируем фрагмент исходного кода:
// Фрагмент с ошибкой в логике
ТекущийЭлемент = НайденныеГруппы.Ссылка.ПолучитьОбъект();
Если Каталог.ОбменТоварами И ТекущийЭлемент <> Неопределено Тогда
Удаление = Новый УдалениеОбъекта(НайденныеГруппы.Ссылка);
Удаление.Записать(); // Объект удален физически!
ПланыОбмена.УдалитьРегистрациюИзменений(МассивУзлов, ТекущийЭлемент); // Попытка удалить регистрацию
КонецЕсли;
Выясним причину неудачи: в тот момент, когда вызывается метод УдалитьРегистрациюИзменений, объект ТекущийЭлемент уже физически удален из базы данных. Ссылка становится невалидной («битой»). Платформа 1С при выполнении метода удаления регистрации может не сопоставить переданную «пустую» ссылку с записью в таблице изменений, либо механизм авторегистрации (если он включен) успевает перехватить событие удаления и заменить регистрацию обычного объекта на регистрацию типа УдалениеОбъекта.
В результате в таблице регистрации изменений (которая в структуре СУБД имеет префикс _Chng) остается запись о том, что объект был удален. Если ваша бизнес-логика подразумевает, что сайт или другая база-приемник вообще не должны знать об этих данных, то такая «битая ссылка» в очереди становится проблемой.
Самый простой и логичный способ избежать появления мусора в плане обмена — сначала очистить очередь регистрации, а только потом удалять сам объект. Рассмотрим правильный алгоритм работы в рамках транзакции.
Посмотрим на исправленный вариант кода:
Процедура УдалитьДанныеПравильно(Каталог) Экспорт
МассивУзлов = Новый Массив;
МассивУзлов.Добавить(ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Каталог, "ТекущийУзел"));
// Получаем данные для удаления
Запрос = Новый Запрос("ВЫБРАТЬ Ссылка ИЗ Справочник.Товары ГДЕ УсловиеУстаревания = ИСТИНА");
Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
Возврат;
КонецЕсли;
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
НачатьТранзакцию();
Попытка
// 1. Сначала удаляем регистрацию из всех необходимых узлов
ПланыОбмена.УдалитьРегистрациюИзменений(МассивУзлов, Выборка.Ссылка);
// 2. Теперь физически удаляем объект
ОбъектОбъект = Выборка.Ссылка.ПолучитьОбъект();
Если ОбъектОбъект <> Неопределено Тогда
ОбъектОбъект.Удалить(); // Или УдалениеОбъекта.Записать()
КонецЕсли;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации("Обмен", УровеньЖурналаРегистрации.Ошибка, , , КраткоеОписаниеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецЦикла;
КонецПроцедуры
В этом случае платформа корректно находит существующую ссылку в таблице регистрации и удаляет её до того, как ссылка превратится в ObjectNotFound.
Если объекты уже были удалены, а «битые ссылки» остались в плане обмена, удалять их по одной в цикле неэффективно. Лучше использовать возможности языка запросов 1С для поиска записей в таблице изменений, которые не имеют соответствия в основной таблице данных. Разберем этот метод подробнее. При необходимости можно воспользоваться обработкой для поиска битых ссылок.
Для каждого объекта метаданных, участвующего в плане обмена, существует виртуальная таблица [ИмяОбъекта].Изменения. Мы можем выполнить ЛЕВОЕ СОЕДИНЕНИЕ этой таблицы с основной таблицей справочника или документа.
Рассмотрим пример запроса для очистки очереди:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Т1.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура.Изменения КАК Т1
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Т2
| ПО Т1.Ссылка = Т2.Ссылка
|ГДЕ
| Т1.Узел = &Узел
| И Т2.Ссылка ЕСТЬ NULL"; // Ссылки нет в основной таблице = битая ссылка
Запрос.УстановитьПараметр("Узел", ТекущийУзел);
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
// Выгружаем колонку со ссылками в массив
МассивБитыхСсылок = РезультатЗапроса.Выгрузить().ВыгрузитьКолонку("Ссылка");
// Пакетное удаление регистрации для всех найденных фантомов
ПланыОбмена.УдалитьРегистрациюИзменений(ТекущийУзел, МассивБитыхСсылок);
КонецЕсли;
Важный момент: метод УдалитьРегистрациюИзменений во втором параметре может принимать как одиночную ссылку, так и массив или таблицу значений. Использование массива значительно ускоряет работу системы и снижает нагрузку на базу данных.
Проанализируем терминологию, чтобы не возникало путаницы. В 1С есть два похожих названия, которые выполняют разные функции:
УдалениеОбъекта — это специальный служебный объект, который используется механизмом обмена данными (например, при выгрузке в XML через ЗаписатьИзменения). Он не является ссылкой на справочник, а служит контейнером для GUID-а удаленного объекта, чтобы принимающая сторона знала, что именно нужно удалить у себя.Удалить() или объект УдалениеОбъекта (как средство удаления) — это способы физического стирания записи из базы.Если в вашем Плане обмена включена Авторегистрация, то при любом физическом удалении (через Объект.Удалить()) платформа автоматически создает в таблице регистрации запись типа УдалениеОбъекта. Если ваша цель — полностью исключить информацию об объекте из обмена, вам придется либо отключать авторегистрацию, либо принудительно вызывать УдалитьРегистрациюИзменений, как было показано выше.
Пока Выборка.Следующий(). Намного эффективнее собрать все ссылки в Массив и передать его одним вызовом.Таким образом, мы выяснили, что ключ к решению проблемы битых ссылок лежит в понимании жизненного цикла ссылки в 1С. Либо мы удаляем регистрацию, пока объект еще "жив", либо используем запросы к виртуальным таблицам изменений для поиска и удаления записей о несуществующих объектах.