Как правильно и безопасно удалять помеченные объекты в 1С без монопольного режима?

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

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

Штатные механизмы БСП: развенчиваем миф о монопольном режиме

Среди многих специалистов до сих пор живёт убеждение, что для удаления помеченных объектов базу данных обязательно нужно переводить в монопольный (исключительный) режим. В старых конфигурациях на платформе 1С 8.1 и 8.2 это действительно было жестким требованием, так как блокировка таблиц накладывалась целиком, мешая работе других пользователей.

Однако в современных версиях Библиотеки стандартных подсистем (БСП), которые используются во всех типовых решениях (УТ 11, ERP, КА 2, БП 3.0), ситуация кардинально изменилась. Рассмотрим ключевые особенности работы современной БСП:

  1. Немонопольный (фоновый) режим работы. Система умеет выполнять поиск ссылок и удаление непосредственно в фоновом режиме. Другие пользователи могут продолжать выписывать документы, создавать элементы справочников и проводить отчеты. БСП накладывает транзакционные блокировки только на те конкретные объекты, которые удаляются в данную секунду.
  2. Удаление порциями. Чтобы не перегружать СУБД и не вызывать таймауты блокировок, удаление происходит небольшими пакетами (транзакциями). Если какой-то объект заблокирован другим пользователем, система просто пропустит его и вернется к нему в следующей итерации.
  3. Регламентные задания. Администратор может настроить автоматический запуск удаления по расписанию (например, каждую ночь в 3:00), используя штатный механизм РегламентныеЗадания.

Таким образом, требование монопольного режима в 1С 8.3 — это опция, а не обязательное условие. Если ваша конфигурация требует монопольности, убедитесь, что вы используете актуальную версию БСП и правильно настроили параметры программы.

Опасности режима ОбменДанными.Загрузка = Истина

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

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

Если мы удаляем, например, элемент справочника Номенклатура с флагом «Загрузка», платформа удалит сам элемент, но в базе останутся «осиротевшие» записи в независимых регистрах (штрихкоды, цены, альтернативные артикулы). Эти записи будут ссылаться на несуществующий уникальный идентификатор (GUID). Это приводит к засорению базы данных и непредсказуемым ошибкам при выполнении последующих запросов.

Разбор ситуации: связка «Номенклатура — Характеристики» и появление «дыр»

Проанализируем классический сценарий, описанный автором темы: при попытке удаления номенклатура удаляется, а подчиненные характеристики остаются в базе, вызывая ошибки в документах поступления. Почему так происходит?

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

Если сторонняя (криво написанная) обработка пытается выполнить прямое удаление через метод УдалитьОбъект() без предварительного транзакционного анализа всей цепочки зависимостей, то может произойти частичное удаление. Владелец удалился, а подчиненная характеристика из-за блокирующей ссылки осталась в базе. В итоге мы получаем нарушенную ссылочную целостность — «дырявые» документы, ссылающиеся на несуществующие объекты.

Вывод: Никогда не используйте сомнительные обработки «быстрого удаления», которые выполняют непосредственное удаление объектов без контроля целостности метаданных всей транзакции!

Использование альтернативных инструментов: Портативные «Инструменты разработчика» (ИР)

Когда штатных возможностей БСП не хватает (например, требуется тонкая зачистка базы после свертки или выборочное удаление тысяч элементов с ручным анализом связей), на помощь приходят профессиональные инструменты. Стандартом де-факто в сообществе 1С является пакет «Инструменты разработчика» (ИР) автора Сергея Старых.

Инструмент «Удаление помеченных объектов с контролем ссылок» обладает уникальными возможностями:

Анализ и оптимизация программного кода удаления ссылок

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

Вот исходный код автора:


&НаСервере
Процедура ДобавитьКУдалениюСсылкиНаОбъектыНаСервере(ТекСтрКонтроль_ИД)
    ДанныеСтрокиКонтроль = Контроль.НайтиПоИдентификатору(ТекСтрКонтроль_ИД);
    
    Для Каждого ТекСтр Из ДанныеСтрокиКонтроль.СсылкиНаОбъект Цикл
        // Только ссылки добавляем.
        Если Найти(ТекСтр.Метаданные, "Справочник.") = 0 
            И Найти(ТекСтр.Метаданные, "Документ.") = 0 
            И Найти(ТекСтр.Метаданные, "ПланСчетов.") = 0 
            И Найти(ТекСтр.Метаданные, "ПланВидовХарактеристик.") = 0 
            И Найти(ТекСтр.Метаданные, "ПланОбмена.") = 0 
            И Найти(ТекСтр.Метаданные, "БизнесПроцесс.") = 0 
            И Найти(ТекСтр.Метаданные, "Задача.") = 0 Тогда
            Продолжить;
        КонецЕсли;
        Если ТекСтр.СписокОбъектов.Количество() < 1 Тогда
            Продолжить;
        КонецЕсли;
        
        ТекОбъект = ТекСтр.СписокОбъектов[0].Значение;
        ИскСтроки = ВыбранныеЭлементы.НайтиСтроки(Новый Структура("Объект, Метаданные", ТекОбъект, ТекСтр.Метаданные));
        Если ИскСтроки.Количество() = 0 Тогда
            НСтр = ВыбранныеЭлементы.Добавить();
            НСтр.Метаданные = ТекСтр.Метаданные;
            НСтр.Объект = ТекОбъект;
            НСтр.Пометка = Истина;
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

&НаКлиенте
Процедура ДобавитьКУдалениюСсылкиНаОбъекты(Команда)
    Для Каждого ДанныеСтрокиКонтроль Из Контроль Цикл
        ДобавитьКУдалениюСсылкиНаОбъектыНаСервере(ДанныеСтрокиКонтроль.ПолучитьИдентификатор());
    КонецЦикла;
    Элементы.ГруппаСтраницы.ТекущаяСтраница = Элементы.ГруппаВыбранныеЭлементы;
КонецПроцедуры

В чем заключается главная проблема этого кода?

Это классическая ошибка 1С-разработчиков: контекстный вызов сервера в цикле на клиенте. Процедура ДобавитьКУдалениюСсылкиНаОбъекты выполняется на клиенте. Внутри нее запускается цикл по коллекции Контроль. На каждой итерации цикла вызывается серверная процедура ДобавитьКУдалениюСсылкиНаОбъектыНаСервере.

Поскольку серверный вызов контекстный (процедура находится в модуле управляемой формы без директивы &НаСервереБезКонтекста), при каждом проходе цикла платформа вынуждена сериализовать всю управляемую форму (все её реквизиты, элементы, таблицы), передать этот огромный XML/JSON-пакет по сети на сервер, выполнить там одну мелкую операцию поиска строки, вернуть форму обратно на клиент и десериализовать её.

Если в таблице Контроль будет хотя бы 200–300 строк, система совершит 300 сетевых раунд-трипов! Пользователь на тонком клиенте (особенно при работе через интернет или VPN) увидит глухое зависание интерфейса на несколько минут. Это абсолютно неприемлемый подход к разработке под управляемый интерфейс.

Как правильно переписать код: один серверный вызов

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

Рассмотрим правильный вариант реализации этой задачи:


&НаСервере
Процедура ДобавитьКУдалениюСсылкиНаОбъектыНаСервере()
    
    // Выполняем весь обход коллекции на сервере за один вызов
    Для Каждого ДанныеСтрокиКонтроль Из Контроль Цикл
        
        Для Каждого ТекСтр Из ДанныеСтрокиКонтроль.СсылкиНаОбъект Цикл
            
            // Оптимизируем проверку типов метаданных через СтрНачинаетсяС
            ИмяМетаданных = ТекСтр.Метаданные;
            Если Не (СтрНачинаетсяС(ИмяМетаданных, "Справочник.") 
                Или СтрНачинаетсяС(ИмяМетаданных, "Документ.") 
                Или СтрНачинаетсяС(ИмяМетаданных, "ПланСчетов.") 
                Или СтрНачинаетсяС(ИмяМетаданных, "ПланВидовХарактеристик.") 
                Или СтрНачинаетсяС(ИмяМетаданных, "ПланОбмена.") 
                Или СтрНачинаетсяС(ИмяМетаданных, "БизнесПроцесс.") 
                Или СтрНачинаетсяС(ИмяМетаданных, "Задача.")) Тогда
                Продолжить;
            КонецЕсли;
            
            Если ТекСтр.СписокОбъектов.Количество() < 1 Тогда
                Продолжить;
            КонецЕсли;
            
            ТекОбъект = ТекСтр.СписокОбъектов[0].Значение;
            
            // Поиск существующих строк для исключения дублирования
            ПараметрыПоиска = Новый Структура("Объект, Метаданные", ТекОбъект, ИмяМетаданных);
            ИскСтроки = ВыбранныеЭлементы.НайтиСтроки(ПараметрыПоиска);
            
            Если ИскСтроки.Количество() = 0 Тогда
                НСтр = ВыбранныеЭлементы.Добавить();
                НСтр.Метаданные = ИмяМетаданных;
                НСтр.Объект = ТекОбъект;
                НСтр.Пометка = Истина;
            КонецЕсли;
            
        КонецЦикла;
        
    КонецЦикла;
    
КонецПроцедуры

&НаКлиенте
Процедура ДобавитьКУдалениюСсылкиНаОбъекты(Команда)
    
    // Один-единственный переход границы "Клиент-Сервер"
    ДобавитьКУдалениюСсылкиНаОбъектыНаСервере();
    
    // Переключение интерфейса на клиенте
    Элементы.ГруппаСтраницы.ТекущаяСтраница = Элементы.ГруппаВыбранныеЭлементы;
    
КонецПроцедуры

Давайте проанализируем, за счет чего мы добились колоссального ускорения работы формы:

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

← На главную