В процессе эксплуатации информационных баз 1С программисты часто сталкиваются с появлением «битых» ссылок. Это ситуации, когда в реквизите объекта хранится уникальный идентификатор (GUID), но сама запись, на которую он ссылается, была удалена из базы данных без контроля ссылочной целостности. В интерфейсе пользователя такие значения отображаются как «Объект не найден (84:...)». Разберем, как эффективно найти такие записи и корректно перезаписать их, чтобы восстановить целостность данных и при этом не затормозить работу системы.
Рассмотрим стандартную ситуацию: нам нужно отобрать элементы справочника ФизическиеЛица, у которых реквизит ГруппаДоступа содержит битую ссылку. Обычное сравнение с пустой ссылкой здесь не поможет, так как битая ссылка не является пустой — она содержит идентификатор, которого просто нет в таблице назначения.
Проанализируем наиболее эффективный способ решения этой задачи через механизм запросов. Когда мы обращаемся через точку к полю ссылочного типа, платформа 1С выполняет левое соединение (LEFT JOIN) с таблицей этого справочника. Если записи в целевой таблице нет, то результатом обращения к полям этой записи будет NULL.
Рассмотрим правильное условие для секции ГДЕ нашего запроса:
ГДЕ
ФизическиеЛица.ГруппаДоступа.Ссылка ЕСТЬ NULL
И НЕ ФизическиеЛица.ГруппаДоступа = ЗНАЧЕНИЕ(Справочник.ГруппыДоступаФизическихЛиц.ПустаяСсылка)
Разберем, почему это работает:
ФизическиеЛица.ГруппаДоступа.Ссылка ЕСТЬ NULL — этот фрагмент кода заставляет платформу попытаться получить поле Ссылка из таблицы справочника ГруппыДоступаФизическихЛиц. Если ссылки в базе не существует, SQL-запрос вернет NULL.И НЕ ФизическиеЛица.ГруппаДоступа = ЗНАЧЕНИЕ(...) — это условие необходимо, чтобы исключить из выборки те записи, где ссылка действительно пустая (т.е. поле осознанно не заполнено пользователем). Пустая ссылка — это вполне легитимное значение, и она не является «битой».Важно отметить, что использование конструкции ПРЕДСТАВЛЕНИЕ(ГруппаДоступа) ПОДОБНО "%Объект не найден%" крайне не рекомендуется. Во-первых, результат функции ПРЕДСТАВЛЕНИЕ зависит от языка сеанса пользователя. Во-вторых, это приводит к значительному замедлению выполнения запроса, так как СУБД приходится преобразовывать идентификаторы в строки для всех записей таблицы.
В исходном коде автора наблюдалась проблема низкой производительности: обработка всего 100 записей занимала слишком много времени. Выясним причину такого поведения. По умолчанию при вызове метода Записать() платформа выполняет целый комплекс действий:
ПередЗаписью, ПриЗаписи).Для ускорения массовой очистки реквизитов мы должны использовать специальный флаг — режим загрузки данных. Установим свойство ОбменДанными.Загрузка в значение Истина. Это скажет платформе, что объект записывается «как есть», и большинство проверок и подписок на события можно пропустить. Это критически важно для производительности.
Разберем теперь полный программный код внешней обработки, который решит поставленную задачу. Мы объединим правильный запрос и оптимизированный цикл обхода коллекции.
&НаСервере
Процедура ИсправитьБитыеСсылкиНаСервере()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ФизическиеЛица.Ссылка КАК Ссылка
|ИЗ
| Справочник.ФизическиеЛица КАК ФизическиеЛица
|ГДЕ
| ФизическиеЛица.ГруппаДоступа.Ссылка ЕСТЬ NULL
| И НЕ ФизическиеЛица.ГруппаДоступа = ЗНАЧЕНИЕ(Справочник.ГруппыДоступаФизическихЛиц.ПустаяСсылка)
| И ФизическиеЛица.ЭтоГруппа = ЛОЖЬ";
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
// Получаем объект для редактирования
ОбъектФЛ = Выборка.Ссылка.ПолучитьОбъект();
// Очищаем битую ссылку, устанавливая пустую ссылку справочника
ОбъектФЛ.ГруппаДоступа = Справочники.ГруппыДоступаФизическихЛиц.ПустаяСсылка;
// Включаем режим загрузки для ускорения и обхода проверок
ОбъектФЛ.ОбменДанными.Загрузка = Истина;
Попытка
ОбъектФЛ.Записать();
Сообщить("Исправлен объект: " + ОбъектФЛ.Наименование);
Исключение
ТекстОшибки = КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
Сообщить("Ошибка при записи объекта " + Выборка.Ссылка + ": " + ТекстОшибки);
КонецПопытки;
КонецЦикла;
Сообщить("Обработка завершена.");
КонецПроцедуры
Проанализируем ситуацию, если использование ОбменДанными.Загрузка = Истина все равно не дает нужного ускорения. В этом случае стоит проверить, не настроены ли в базе активные планы обмена. Каждая запись объекта создает запись в системных таблицах регистрации изменений. Если вы уверены, что эти исправления не нужно передавать в другие базы (например, в бухгалтерию или филиалы), можно временно отключить регистрацию изменений для данного набора данных.
Если ваша конфигурация построена на базе Библиотеки стандартных подсистем (БСП), задачу можно решить еще проще, не прибегая к написанию программного кода. Посмотрим на алгоритм действий через типовую обработку «Групповое изменение реквизитов»:
Физические лица.Группа доступа.Ссылка — Не заполнено И Группа доступа — Заполнено. Этот фильтр как раз и отберет «битые» ссылки.Группа доступа и установите ему значение «Пустая ссылка» (или просто оставьте пустым).Этот способ является наиболее безопасным и предпочтительным, так как механизмы БСП уже содержат все необходимые оптимизации для работы с большими массивами данных.
В завершение разберем причины возникновения этой проблемы, чтобы не допускать ее в будущем. Чаще всего ссылки «бьются» в следующих случаях:
1. Удаление объектов без контроля целостности. Если программист или администратор использует метод Удалить() вместо УстановитьПометкуУдаления(), платформа физически стирает запись из таблицы базы данных, не проверяя, используется ли она в других местах.
2. Ошибки синхронизации. Если при обмене данными объект-владелец (например, Физлицо) пришел в базу, а связанный с ним объект (Группа доступа) по каким-то причинам был отфильтрован или не выгружен, в целевой базе образуется битая ссылка.
3. Прерывание транзакций при тестировании и исправлении (ТиИ). Если во время работы утилиты «Тестирование и исправление» происходит сбой, индексы могут быть нарушены, что приведет к ложному отображению объектов как найденных или не найденных.
Таким образом, мы выяснили, что для быстрого и качественного исправления данных необходимо сочетать точечный поиск через запрос с использованием ЕСТЬ NULL и скоростную запись с флагом ОбменДанными.Загрузка.