Массовое удаление документов, таких как входящие электронные письма, часто становится серьезной проблемой для высоконагруженных систем. Когда количество объектов исчисляется миллионами, удаление в один поток может занимать недопустимо много времени. Однако при попытке распараллелить процесс с помощью фоновых заданий разработчики неизбежно сталкиваются с ошибкой: "Конфликт блокировок при выполнении транзакции: Неустранимый конфликт блокировок". Рассмотрим подробнее, почему это происходит и как выстроить архитектуру удаления правильно.
Разберем природу конфликта. При вызове метода Удалить() платформа 1С выполняет неявную проверку ссылочной целостности. Она сканирует все таблицы базы данных, где теоретически может встретиться ссылка на удаляемый объект. В многопоточном режиме ситуация осложняется следующим:
Для того чтобы система не "зависала" на ожидании, мы можем явно указать платформе, какой объект мы собираемся модифицировать. Использование исключительной блокировки позволяет другим потокам сразу понять, что объект занят, и не пытаться его обрабатывать. Проанализируем пример кода для фонового задания:
Попытка
НачатьТранзакцию(РежимУправленияБлокировкойДанных.Управляемый);
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Документ.ЭлектронноеПисьмоВходящее");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.УстановитьЗначение("Ссылка", ТекущаяСсылка);
Блокировка.Заблокировать();
// Получаем объект и удаляем его
ДокументОбъект = ТекущаяСсылка.ПолучитьОбъект();
Если ДокументОбъект <> Неопределено Тогда
ДокументОбъект.Удалить();
КонецЕсли;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
// Логируем пропуск объекта или добавляем в очередь на повтор
КонецПопытки;
Важный нюанс: Исключительный режим блокировки гарантирует, что ни одна другая транзакция не сможет даже прочитать этот объект до завершения удаления. Это эффективный способ борьбы с дедлоками (deadlock).
Если документы связаны в цепочки (например, переписка), простое разделение списка поровну между потоками приведет к конфликтам. Выясним причину: один поток может обрабатывать "верхушку" дерева, а другой — "корень". Правильнее всего будет сначала определить иерархию. Рассмотрим вариант запроса, который помогает найти "предка" для каждого письма:
ВЫБРАТЬ
ВзаимодействиеОснование КАК НачалоДуги,
Ссылка КАК КонецДуги
ПОМЕСТИТЬ ЗамыканияДлины1
ИЗ
Документ.ЭлектронноеПисьмоВходящее
ГДЕ
ВзаимодействиеОснование <> ЗНАЧЕНИЕ(Документ.ЭлектронноеПисьмоВходящее.ПустаяСсылка);
// Далее через соединения таблицы самой с собой получаем связи на глубину 2, 4, 8 и т.д.
// Это позволяет выявить всю цепочку переписки и назначить ее целиком одному потоку.
Проанализируем стратегию распределения данных: если мы выделим каждый "куст" связанных документов в отдельный поток, пересечения по блокировкам будут минимальны. Если построение полной иерархии слишком ресурсоемко, используем упрощенный подход: удалять документы строго с конца цепочки к ее началу. Для автоматического анализа и очистки цепочек можно использовать инструмент для удаления документов с построением дерева связанных объектов.
Часто конфликт возникает не в самой таблице документов, а в независимых регистрах сведений (например, версионирование или связи объектов CRM). Если мы удаляем объект, платформа проверяет эти регистры. Попробуем оптимизировать этот процесс, очищая регистры явно перед удалением документа:
НаборЗаписей = РегистрыСведений.СвязиОбъектов.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Объект.Установить(ТекущаяСсылка);
НаборЗаписей.Записать(); // Очищаем без замещения
// После этого удаление документа пройдет гораздо "легче"
ДокументОбъект.Удалить();
Совет: Внутри транзакции также стоит устанавливать блокировку на набор записей регистра, используя измерения отбора.
Вместо перебора объектов в цикле и вызова метода Удалить() у каждого, проанализируем использование метода глобального контекста УдалитьОбъекты(). Этот метод позволяет передать массив ссылок. Платформа оптимизирует запросы к СУБД, выполняя проверки ссылочной целостности пачками, что значительно сокращает количество транзакционных вызовов.
МассивСсылок = Новый Массив;
// Заполняем массив порцией данных (например, по 100-500 штук)
УдалитьОбъекты(МассивСсылок, Истина); // Истина - проверять ссылки
Рассмотрим, как лучше организовать работу фоновых заданий:
ДокументОбъект.ОбменДанными.Загрузка = Истина;. Это отключит прикладные проверки в модулях объекта (но не проверку ссылочной целостности платформой), что ускорит процесс.Выясним итог: многопоточное удаление в 1С требует тщательной подготовки данных. Использование управляемых блокировок в режиме Исключительный в сочетании с предварительным анализом связей позволяет сократить время очистки базы в несколько раз, сохраняя стабильность системы. В качестве готового решения можно использовать обработку для многопоточного удаления документов и очистки регистров.