Проблема удаления помеченных объектов в платформе 1С:Предприятие — одна из самых частых и болезненных задач, с которыми сталкиваются как администраторы систем, так и разработчики. Стандартные механизмы конфигураций не всегда ведут себя очевидным образом, особенно когда речь заходит о связанных справочниках, независимых регистрах сведений и работе пользователей в реальном времени. В этой статье мы подробно разберем, как устроено удаление помеченных объектов, почему возникают логические ошибки («дыры») в базе данных, как правильно использовать современные инструменты и как исправить типичные архитектурные ошибки в кодовой базе.
Среди многих специалистов до сих пор живёт убеждение, что для удаления помеченных объектов базу данных обязательно нужно переводить в монопольный (исключительный) режим. В старых конфигурациях на платформе 1С 8.1 и 8.2 это действительно было жестким требованием, так как блокировка таблиц накладывалась целиком, мешая работе других пользователей.
Однако в современных версиях Библиотеки стандартных подсистем (БСП), которые используются во всех типовых решениях (УТ 11, ERP, КА 2, БП 3.0), ситуация кардинально изменилась. Рассмотрим ключевые особенности работы современной БСП:
РегламентныеЗадания.Таким образом, требование монопольного режима в 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 Тогда
НСтр = ВыбранныеЭлементы.Добавить();
НСтр.Метаданные = ИмяМетаданных;
НСтр.Объект = ТекОбъект;
НСтр.Пометка = Истина;
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецПроцедуры
&НаКлиенте
Процедура ДобавитьКУдалениюСсылкиНаОбъекты(Команда)
// Один-единственный переход границы "Клиент-Сервер"
ДобавитьКУдалениюСсылкиНаОбъектыНаСервере();
// Переключение интерфейса на клиенте
Элементы.ГруппаСтраницы.ТекущаяСтраница = Элементы.ГруппаВыбранныеЭлементы;
КонецПроцедуры
Давайте проанализируем, за счет чего мы добились колоссального ускорения работы формы:
N (где N — число строк в таблице контроля) до 1. Скорость работы процедуры увеличилась в десятки раз.Найти() мы применили современную и более быструю функцию СтрНачинаетсяС(), что улучшило читаемость кода.Используя этот паттерн проектирования, вы всегда будете создавать быстродействующие и отзывчивые интерфейсы, которые не заставляют пользователей ждать окончания сетевых транзакций.