Почему после программного удаления документы остаются в списке и как убрать фантомные записи?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

В практике администрирования и разработки на платформе 1С:Предприятие, особенно при работе с крупными конфигурациями вроде «Управление торговлей 11» (УТ 11), «ERP Управление предприятием» или «Комплексная автоматизация», специалисты часто сталкиваются с неочевидным поведением системы. Одной из таких проблем является появление «фантомных» документов в списках. Ситуация выглядит так: документ был удален (чаще всего программно или специальной обработкой), физически объекта в базе данных больше нет (при попытке открытия выдается сообщение «Объект не найден»), но строка с номером, датой и суммой все еще отображается в журнале документов.

В этой статье мы подробно разберем, почему возникает данная ситуация, как устроена архитектура списков в современных конфигурациях и, самое главное, как правильно устранить последствия некорректного удаления и избежать их в будущем.

Суть проблемы: удаление объекта vs удаление записи в реестре

Давайте проанализируем ситуацию, описанную в теме. Пользователь выполнил удаление большого массива документов (4 800 000 записей) «напрямую», используя внешнюю обработку. При этом, скорее всего, использовался режим отключения проверок или режим ОбменДанными.Загрузка = Истина для ускорения процесса. В результате документы исчезли как объекты базы данных, но остались в форме списка.

Чтобы понять причину, нам нужно углубиться в архитектуру построения интерфейсов в конфигурациях на базе БСП (Библиотека стандартных подсистем).

Архитектура списков в УТ 11, ERP и КА

В старых или простых конфигурациях динамический список в форме документа обычно обращался напрямую к таблице этого документа. Запрос выглядел примерно так:


ВЫБРАТЬ
    Ссылка,
    Номер,
    Дата,
    Контрагент
ИЗ
    Документ.РеализацияТоваровУслуг

Однако в современных, высоконагруженных системах такой подход часто оказывается неэффективным, особенно когда нужно показать общий журнал документов (например, «Документы продажи»), где собираются данные из разных таблиц (Реализация, Акт выполненных работ, Возврат и т.д.). Использование конструкции ОБЪЕДИНИТЬ ВСЕ для десятка таблиц приводило бы к существенному замедлению работы.

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

  1. РеестрДокументов — общий регистр, содержащий основные реквизиты (Номер, Дата, Сумма, Организация, Партнер) и ссылку на сам документ.
  2. Специализированные реестры, например, ЖурналДокументовПродажи.

Кроме того, для реализации производительного механизма RLS (ограничения прав доступа на уровне записей) используются регистры ключей доступа, например, КлючиРеестраДокументов. Динамический список в форме настроен так, что он делает выборку именно из этих регистров, соединяя их с таблицей документов левым соединением (или даже без соединения, если все данные есть в регистре).

Почему возникают «фантомы»?

Когда вы удаляете документ стандартным интерактивным способом (пометка на удаление -> удаление помеченных объектов), система запускает транзакцию удаления. В этот момент срабатывают Подписки на события (Event Subscriptions), например, ПередУдалением или ПриУдалении.

Эти подписки отвечают за целостность данных: они находят соответствующие записи в регистрах сведений (Реестр документов, Ключи доступа) и удаляют их синхронно с удалением самого документа.

Проблема возникает, когда удаление происходит «грубо»:

  1. Используется метод Удалить() объекта в режиме загрузки данных (ОбменДанными.Загрузка = Истина). В этом режиме подписки на события и проверки отключаются для скорости.
  2. Удаление происходит прямыми SQL-запросами (крайне не рекомендуется, но встречается) или некорректно написанными обработками.

В итоге:

Динамический список считывает данные из регистра сведений. Он видит запись, видит номер и дату, и выводит их на экран. При попытке кликнуть по строке система пытается получить объект по битой ссылке и выдает ошибку «Объект не найден».

Способы решения проблемы

Рассмотрим несколько методов исправления ситуации, от штатных до программных.

Способ 1: Тестирование и исправление (Штатный)

Это самый безопасный и рекомендуемый способ, если у вас есть возможность монопольного доступа к базе данных и время на выполнение процедуры.

  1. Запустите Конфигуратор.
  2. Перейдите в меню Администрирование -> Тестирование и исправление.
  3. В открывшемся окне нас интересует пункт Проверка ссылочной целостности.
  4. В настройках этого пункта выберите вариант: При наличии ссылок на несуществующие объекты -> Очищать ссылки (или «Удалять объект», если речь идет о записи регистра сведений, которая целиком зависит от битой ссылки).
  5. Нажмите «Выполнить».

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

Способ 2: Программная очистка регистра (Оптимальный для больших баз)

Если запуск полного ТиИ невозможен, нам необходимо написать небольшую обработку, которая точечно очистит «мусор» в конкретном регистре. Давайте разберем алгоритм такой обработки.

Нам нужно найти записи в регистре сведений, где измерение (обычно это поле Ссылка или аналогичное) указывает на NULL (в терминах запроса при соединении) или является битой ссылкой.

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


// Пример алгоритма очистки реестра
// 1. Выбираем записи, где ссылка на документ "битая"

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ РАЗЛИЧНЫЕ
    |   Реестр.Ссылка КАК БитаяСсылка
    |ИЗ
    |   РегистрСведений.РеестрДокументов КАК Реестр
    |ГДЕ
    |   Реестр.Ссылка.Ссылка ЕСТЬ NULL"; 
    // Условие "Ссылка.Ссылка ЕСТЬ NULL" — эффективный способ найти битые ссылки в запросе
    
Выборка = Запрос.Выполнить().Выбрать();

НаборЗаписей = РегистрыСведений.РеестрДокументов.СоздатьНаборЗаписей();

Пока Выборка.Следующий() Цикл
    
    // В данном регистре измерение часто одно - Ссылка.
    // Устанавливаем отбор по битой ссылке
    НаборЗаписей.Отбор.Ссылка.Установить(Выборка.БитаяСсылка);
    
    // Записываем пустой набор, что равносильно удалению записей с этим отбором
    НаборЗаписей.Записать();
    
КонецЦикла;

Важное примечание: Если в регистре измерений несколько (например, Организация + Документ), вам нужно будет выбирать все измерения в запросе и устанавливать отбор по всем измерениям. В типовых конфигурациях в РеестрДокументов ведущим измерением обычно является ссылка на документ.

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

Способ 3: Правильное удаление больших объемов данных

Чтобы не сталкиваться с такой проблемой в будущем, давайте разберем, как правильно удалять большие объемы данных (например, 4.8 млн документов), не "ломая" базу.

Если вы используете ОбменДанными.Загрузка = Истина, вы обязаны самостоятельно позаботиться о ссылочной целостности. Это значит, что ваш скрипт удаления должен выглядеть не просто как ДокОбъект.Удалить(), а включать очистку зависимых данных.

Однако, более безопасным подходом является использование стандартного механизма, но в пакетном режиме, чтобы избежать переполнения лога транзакций и памяти.

Алгоритм безопасного массового удаления:

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

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


// Плохой подход (оставляет хвосты в реестрах сведений)
ДокОбъект = СсылкаНаДок.ПолучитьОбъект();
ДокОбъект.ОбменДанными.Загрузка = Истина;
ДокОбъект.Удалить(); 

// Более правильный подход (если необходима скорость)
// 1. Сначала отменяем проведение (очищает движения регистров накопления и бухгалтерии)
ДокОбъект.Записать(РежимЗаписиДокумента.ОтменаПроведения); 
// 2. Затем удаляем
ДокОбъект.Удалить(); 
// Но даже это может не очистить "Реестр документов", если отключены события.

Поэтому вывод однозначен: при "жестком" удалении с отключением проверок вы берете на себя ответственность за очистку вспомогательных таблиц (регистров сведений), на которых базируется интерфейс УТ 11 / ERP.

Резюме

Если вы видите удаленные документы в списке:

  1. Проблема не в кэше (хотя его очистка тоже не помешает), а в записях регистра сведений РеестрДокументов.
  2. Причина — удаление документов в обход стандартных механизмов платформы (без вызова подписок на события).
  3. Решение — найти и удалить записи регистра, ссылающиеся на NULL (битые ссылки).
← На главную