В практике администрирования и разработки на платформе 1С:Предприятие, особенно при работе с крупными конфигурациями вроде «Управление торговлей 11» (УТ 11), «ERP Управление предприятием» или «Комплексная автоматизация», специалисты часто сталкиваются с неочевидным поведением системы. Одной из таких проблем является появление «фантомных» документов в списках. Ситуация выглядит так: документ был удален (чаще всего программно или специальной обработкой), физически объекта в базе данных больше нет (при попытке открытия выдается сообщение «Объект не найден»), но строка с номером, датой и суммой все еще отображается в журнале документов.
В этой статье мы подробно разберем, почему возникает данная ситуация, как устроена архитектура списков в современных конфигурациях и, самое главное, как правильно устранить последствия некорректного удаления и избежать их в будущем.
Давайте проанализируем ситуацию, описанную в теме. Пользователь выполнил удаление большого массива документов (4 800 000 записей) «напрямую», используя внешнюю обработку. При этом, скорее всего, использовался режим отключения проверок или режим ОбменДанными.Загрузка = Истина для ускорения процесса. В результате документы исчезли как объекты базы данных, но остались в форме списка.
Чтобы понять причину, нам нужно углубиться в архитектуру построения интерфейсов в конфигурациях на базе БСП (Библиотека стандартных подсистем).
В старых или простых конфигурациях динамический список в форме документа обычно обращался напрямую к таблице этого документа. Запрос выглядел примерно так:
ВЫБРАТЬ
Ссылка,
Номер,
Дата,
Контрагент
ИЗ
Документ.РеализацияТоваровУслуг
Однако в современных, высоконагруженных системах такой подход часто оказывается неэффективным, особенно когда нужно показать общий журнал документов (например, «Документы продажи»), где собираются данные из разных таблиц (Реализация, Акт выполненных работ, Возврат и т.д.). Использование конструкции ОБЪЕДИНИТЬ ВСЕ для десятка таблиц приводило бы к существенному замедлению работы.
Разработчики фирмы 1С внедрили оптимизацию. Списки документов теперь строятся не по самим документам, а по специальным регистрам сведений. Основные из них:
РеестрДокументов — общий регистр, содержащий основные реквизиты (Номер, Дата, Сумма, Организация, Партнер) и ссылку на сам документ.ЖурналДокументовПродажи.Кроме того, для реализации производительного механизма RLS (ограничения прав доступа на уровне записей) используются регистры ключей доступа, например, КлючиРеестраДокументов. Динамический список в форме настроен так, что он делает выборку именно из этих регистров, соединяя их с таблицей документов левым соединением (или даже без соединения, если все данные есть в регистре).
Когда вы удаляете документ стандартным интерактивным способом (пометка на удаление -> удаление помеченных объектов), система запускает транзакцию удаления. В этот момент срабатывают Подписки на события (Event Subscriptions), например, ПередУдалением или ПриУдалении.
Эти подписки отвечают за целостность данных: они находят соответствующие записи в регистрах сведений (Реестр документов, Ключи доступа) и удаляют их синхронно с удалением самого документа.
Проблема возникает, когда удаление происходит «грубо»:
Удалить() объекта в режиме загрузки данных (ОбменДанными.Загрузка = Истина). В этом режиме подписки на события и проверки отключаются для скорости.В итоге:
Документ.РеализацияТоваровУслуг уничтожается.РегистрСведений.РеестрДокументов остается. В ней поле «Ссылка» теперь содержит битую ссылку (GUID удаленного объекта), но поля «Номер», «Дата», «Сумма» остаются заполненными текстом/числами.Динамический список считывает данные из регистра сведений. Он видит запись, видит номер и дату, и выводит их на экран. При попытке кликнуть по строке система пытается получить объект по битой ссылке и выдает ошибку «Объект не найден».
Рассмотрим несколько методов исправления ситуации, от штатных до программных.
Это самый безопасный и рекомендуемый способ, если у вас есть возможность монопольного доступа к базе данных и время на выполнение процедуры.
Система просканирует все таблицы, найдет записи в РеестрДокументов, которые ссылаются на несуществующие документы, и удалит эти записи. Однако, как было отмечено в обсуждении, если объем данных огромен (миллионы документов), этот процесс может занять недопустимо много времени.
Если запуск полного ТиИ невозможен, нам необходимо написать небольшую обработку, которая точечно очистит «мусор» в конкретном регистре. Давайте разберем алгоритм такой обработки.
Нам нужно найти записи в регистре сведений, где измерение (обычно это поле Ссылка или аналогичное) указывает на NULL (в терминах запроса при соединении) или является битой ссылкой.
Рассмотрим пример кода для очистки регистра РеестрДокументов. Обратите внимание, что точное имя регистра и измерения может отличаться в зависимости от версии вашей конфигурации, поэтому предварительно посмотрите в конфигураторе точное название объекта метаданных.
// Пример алгоритма очистки реестра
// 1. Выбираем записи, где ссылка на документ "битая"
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| Реестр.Ссылка КАК БитаяСсылка
|ИЗ
| РегистрСведений.РеестрДокументов КАК Реестр
|ГДЕ
| Реестр.Ссылка.Ссылка ЕСТЬ NULL";
// Условие "Ссылка.Ссылка ЕСТЬ NULL" — эффективный способ найти битые ссылки в запросе
Выборка = Запрос.Выполнить().Выбрать();
НаборЗаписей = РегистрыСведений.РеестрДокументов.СоздатьНаборЗаписей();
Пока Выборка.Следующий() Цикл
// В данном регистре измерение часто одно - Ссылка.
// Устанавливаем отбор по битой ссылке
НаборЗаписей.Отбор.Ссылка.Установить(Выборка.БитаяСсылка);
// Записываем пустой набор, что равносильно удалению записей с этим отбором
НаборЗаписей.Записать();
КонецЦикла;
Важное примечание: Если в регистре измерений несколько (например, Организация + Документ), вам нужно будет выбирать все измерения в запросе и устанавливать отбор по всем измерениям. В типовых конфигурациях в РеестрДокументов ведущим измерением обычно является ссылка на документ.
Также стоит проверить регистры ключей доступа, если проблема сохраняется:
РегистрСведений.КлючиРеестраДокументовРегистрСведений.КлючиДоступаКРегистрамЧтобы не сталкиваться с такой проблемой в будущем, давайте разберем, как правильно удалять большие объемы данных (например, 4.8 млн документов), не "ломая" базу.
Если вы используете ОбменДанными.Загрузка = Истина, вы обязаны самостоятельно позаботиться о ссылочной целостности. Это значит, что ваш скрипт удаления должен выглядеть не просто как ДокОбъект.Удалить(), а включать очистку зависимых данных.
Однако, более безопасным подходом является использование стандартного механизма, но в пакетном режиме, чтобы избежать переполнения лога транзакций и памяти.
Алгоритм безопасного массового удаления:
ПометкаУдаления). При этом запустятся подписки, которые корректно обновят реестры сведений (запись останется, но может поменяться признак).УдалениеПомеченныхОбъектов в монопольном режиме или через фоновое задание частями. Это обеспечит корректную очистку всех хвостов.Если же вы все-таки удаляете объекты программно и безвозвратно, убедитесь, что вы также удаляете движения документа по всем регистрам:
// Плохой подход (оставляет хвосты в реестрах сведений)
ДокОбъект = СсылкаНаДок.ПолучитьОбъект();
ДокОбъект.ОбменДанными.Загрузка = Истина;
ДокОбъект.Удалить();
// Более правильный подход (если необходима скорость)
// 1. Сначала отменяем проведение (очищает движения регистров накопления и бухгалтерии)
ДокОбъект.Записать(РежимЗаписиДокумента.ОтменаПроведения);
// 2. Затем удаляем
ДокОбъект.Удалить();
// Но даже это может не очистить "Реестр документов", если отключены события.
Поэтому вывод однозначен: при "жестком" удалении с отключением проверок вы берете на себя ответственность за очистку вспомогательных таблиц (регистров сведений), на которых базируется интерфейс УТ 11 / ERP.
Если вы видите удаленные документы в списке:
РеестрДокументов.NULL (битые ссылки).