При выполнении массового удаления объектов в прикладных решениях 1С разработчики часто сталкиваются с ситуацией, когда процедура завершается ошибкой: «База данных заблокирована, сеанс такой-то, приложение Фоновое задание». Разберем механизм возникновения этой коллизии и детально проанализируем эффективные способы обхода ограничений монопольного режима.
Вызов стандартного платформенного метода УдалитьОбъекты(МассивСсылок, Истина, Найденные) жестко требует установки монопольного режима через УстановитьМонопольныйРежим(Истина), поскольку второй параметр Проверять = Истина запускает встроенный механизм контроля ссылочной целостности платформы. Однако на практике установка монопольного режима из пользовательского сеанса не гарантирует полной изоляции данных:
УстановитьМонопольныйРежим(Истина) запрещает интерактивный вход новых пользователей, но планировщик кластера серверов 1С продолжает запускать регламентные операции, порождая фоновые задания.ПодключитьОбработчикОжидания(), которые асинхронно порождают фоновые задачи.Рассмотрим практические варианты обхода этой проблемы, позволяющие безопасно выполнить удаление данных.
Если удаление выполняется через внешнюю обработку в интерактивном сеансе, самым быстрым способом является временное отключение автозапуска фоновых заданий конфигурации при старте программы.
Запустим сеанс «1С:Предприятия» через командную строку или добавим параметр в настройках запуска базы данных:
1cv8c.exe ENTERPRISE /IBName "МояБаза" /DisableStartupJobs
Параметр /DisableStartupJobs подавляет запуск стартовых регламентных процедур и фоновых задач, инициируемых типовыми подсистемами при входе пользователя. В результате клиентские таймеры не запускают конкурирующие серверные потоки, и монопольное удаление отрабатывает без конфликтов.
Платформа 1С требует монопольный режим исключительно тогда, когда метод УдалитьОбъекты() выполняет проверку ссылок собственными силами. Если передать вторым параметром значение Ложь, требование к монопольному режиму снимается. Чтобы не нарушить ссылочную целостность базы данных, разделим задачу на два контролируемых этапа:
НайтиПоСсылкам().Посмотрим на пример реализации такого алгоритма:
// 1. Формируем массив объектов для проверки
МассивДляПоиска = Новый Массив;
Для Каждого Ссылка Из МассивСсылок Цикл
МассивДляПоиска.Добавить(Ссылка);
КонецЦикла;
// 2. Ищем ссылки на удаляемые объекты
ТаблицаСсылок = НайтиПоСсылкам(МассивДляПоиска);
// 3. Отбираем только те объекты, на которые нет сторонних ссылок
МассивКБезусловномуУдалению = Новый Массив;
Для Каждого Ссылка Из МассивДляПоиска Цикл
Отбор = Новый Структура("Ссылка", Ссылка);
СтрокиСсылок = ТаблицаСсылок.НайтиСтроки(Отбор);
// Если ссылок нет или все ссылки ведут только внутри удаляемого списка
ЕстьВнешниеСсылки = Ложь;
Для Каждого Стр Из СтрокиСсылок Цикл
Если МассивДляПоиска.Найти(Стр.Данные) = Неопределено Тогда
ЕстьВнешниеСсылки = Истина;
Прервать;
КонецЕсли;
КонецЦикла;
Если Не ЕстьВнешниеСсылки Тогда
МассивКБезусловномуУдалению.Добавить(Ссылка);
КонецЕсли;
КонецЦикла;
// 4. Непосредственное удаление без требования монопольного режима
Если МассивКБезусловномуУдалению.Количество() > 0 Тогда
УдалитьОбъекты(МассивКБезусловномуУдалению, Ложь);
КонецЕсли;
Этот подход устраняет необходимость в вызове УстановитьМонопольныйРежим(Истина), а значит, работа фоновых заданий не прервет процесс удаления.
При непосредственном вызове УдалитьОбъекты(..., Ложь) платформа физически стирает записи из таблиц без вызова модулей объектов и подписок на события. Если удаляемые сущности содержат движения в регистрах или требуют каскадной очистки подчиненных данных, правильнее использовать объектное удаление.
Разберем по шагам, как организовать безопасное удаление порциями (транзакциями), чтобы избежать эскалации блокировок СУБД:
РазмерПорции = 200;
Счетчик = 0;
НачатьТранзакцию();
Попытка
Для Каждого ТекСсылка Из МассивКБезусловномуУдалению Цикл
Объект = ТекСсылка.ПолучитьОбъект();
Если Объект <> Неопределено Тогда
Объект.Удалить();
Счетчик = Счетчик + 1;
КонецЕсли;
Если Счетчик % РазмерПорции = 0 Тогда
ЗафиксироватьТранзакцию();
НачатьТранзакцию();
КонецЕсли;
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации("УдалениеДанных", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
ВызватьИсключение;
КонецПопытки;
Разбиение массива на небольшие пакеты освобождает блокировки таблиц на уровне СУБД после каждой порции и предотвращает конфликт с параллельными фоновыми процессами.
Если бизнес-логика требует исключительно штатного монопольного удаления через УдалитьОбъекты(..., Истина), запуск фоновых процессов необходимо заблокировать на уровне инфраструктуры:
ScheduledJobsDenied = True).БлокировкаСеансовИнформационнойБазы, вызвать УстановитьБлокировкуСеансов() с указанием параметра КодРазрешения, завершить текущие пользовательские сеансы и выполнить обработку удаления из служебного сеанса с ключом /UC.ОтключитьОбработчикОжидания() для известных таймеров конфигурации.Таким образом, комбинация предварительного анализа связей через НайтиПоСсылкам() с последующим немонопольным порционным удалением является наиболее надежной стратегией при работе в клиент-серверных базах данных с высокой активностью фоновых задач.