Массовое удаление строк из регистра сведений — регулярная задача при очистке исторических данных, исправлении ошибок интеграции или оптимизации базы. Когда таблица содержит миллионы записей, использование объекта РегистрСведенийМенеджерЗаписи становится неэффективным: единичные операции вызывают колоссальные накладные расходы и длятся часами. Однако при попытке оптимизировать процедуру разработчики часто сталкиваются с неочевидными ограничениями стандартного набора записей.
Проанализируем типичную ошибку при работе с отборами набора записей, разберем причины ее возникновения и рассмотрим современные высокопроизводительные методы частичной очистки регистров сведений.
Посмотрим на распространенную логическую ошибку: разработчик выбирает запросом идентификаторы удаляемых строк, делит их на пачки и пытается передать порцию значений в отбор набора записей:
НаборЗаписей = РегистрыСведений.ШтрихкодыНоменклатуры.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Штрихкод.Установить(МассивПорция500); // Ошибка
НаборЗаписей.Записать();
Выясним причину, по которой этот код не работает. Коллекция Отбор у объекта РегистрСведенийНаборЗаписей устроена иначе, чем отборы в динамических списках или отчетах. В элементе отбора набора записей поддерживается исключительно проверка на точное равенство единственному значению (вид сравнения Равно). Метод Установить() принимает конкретное значение, а не массив или список. Если передать массив, платформа воспримет его как попытку найти запись, где в поле записана ссылка на сам объект типа Массив, в результате чего отбор вернет пустой результат, либо платформа выбросит исключение несоответствия типов.
Ситуация осложняется еще сильнее, если условие фильтрации строится не по измерениям, а по ресурсу или реквизиту (в исходном примере — по ресурсу УТР_Серия). В наборе записей регистра сведений отбор можно установить только по полям, входящим в состав измерений (а также по регистратору и периоду для подчиненных и периодических регистров). По ресурсам и реквизитам фильтрация на уровне стандартного набора записей невозможна в принципе.
Начиная с версии платформы 8.3.25, в платформе 1С:Предприятие появился штатный высокопроизводительный механизм работы с наборами — расширенные режимы записи. Он специально проектировался для массовой вставки, изменения и выборочного удаления строк в высоконагруженных регистрах.
Для включения функционала используется свойство РасширенныеРежимыЗамещения, а сам тип операции задается системным перечислением РежимЗамещения. Чтобы удалить только требуемые записи без полной перезаписи регистра, воспользуемся значением РежимЗамещения.Удаление.
Разберем по шагам, как устроен этот алгоритм:
РегистрСведенийНаборЗаписей и активируем свойство РасширенныеРежимыЗамещения = Истина;.Записать(РежимЗамещения.Удаление).Посмотрим на пример базовой реализации:
&НаСервере
Процедура УдалитьНенужныеШтрихкоды()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ШтрихкодыНоменклатуры.Штрихкод КАК Штрихкод,
| ШтрихкодыНоменклатуры.Номенклатура КАК Номенклатура,
| ШтрихкодыНоменклатуры.Характеристика КАК Характеристика,
| ШтрихкодыНоменклатуры.Упаковка КАК Упаковка
|ИЗ
| РегистрСведений.ШтрихкодыНоменклатуры КАК ШтрихкодыНоменклатуры
|ГДЕ
| НЕ ШтрихкодыНоменклатуры.УТР_Серия = ЗНАЧЕНИЕ(Справочник.СерииНоменклатуры.ПустаяСсылка)";
ТЗ_Удаляемые = Запрос.Выполнить().Выгрузить();
Если ТЗ_Удаляемые.Количество() = 0 Тогда
Возврат;
КонецЕсли;
НаборЗаписей = РегистрыСведений.ШтрихкодыНоменклатуры.СоздатьНаборЗаписей();
НаборЗаписей.РасширенныеРежимыЗамещения = Истина;
// Отключаем подписки и регистрацию изменений для максимальной скорости
НаборЗаписей.ОбменДанными.Загрузка = Истина;
НаборЗаписей.Загрузить(ТЗ_Удаляемые);
НаборЗаписей.Записать(РежимЗамещения.Удаление);
КонецПроцедуры
На уровне СУБД этот механизм работает принципиально иначе, чем стандартная перезапись: строки загружаются во временную таблицу базы данных, после чего выполняется удаление через внутреннее соединение по измерениям регистра (DELETE ... FROM ... JOIN ...). Это исключает медленную построчную обработку и существенно экономит ресурсы сервера.
Если в регистре необходимо удалить не десятки тысяч, а миллионы записей, загрузка всех строк в одну таблицу значений за один прием создает критические риски:
rphost может вызвать дефицит RAM и аварийное завершение процессов сервера.Чтобы избежать этих проблем, выполним удаление циклически, разбивая поток на порции фиксированного размера (обычно от 5 000 до 50 000 записей за транзакцию). Обязательно убедимся, что по полю ресурса, участвующему в условии отбора, в конфигурации включено свойство Индексировать, иначе поиск каждой порции приведет к тяжелому полному сканированию таблицы регистра.
Рассмотрим надежный шаблон порционного удаления:
&НаСервере
Процедура УдалитьЗаписиПорциями()
РазмерПачки = 10000;
Пока Истина Цикл
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ " + Формат(РазмерПачки, "ЧГ=0") + "
| ШтрихкодыНоменклатуры.Штрихкод КАК Штрихкод,
| ШтрихкодыНоменклатуры.Номенклатура КАК Номенклатура,
| ШтрихкодыНоменклатуры.Характеристика КАК Характеристика,
| ШтрихкодыНоменклатуры.Упаковка КАК Упаковка
|ИЗ
| РегистрСведений.ШтрихкодыНоменклатуры КАК ШтрихкодыНоменклатуры
|ГДЕ
| НЕ ШтрихкодыНоменклатуры.УТР_Серия = ЗНАЧЕНИЕ(Справочник.СерииНоменклатуры.ПустаяСсылка)";
ТЗ_Порция = Запрос.Выполнить().Выгрузить();
Если ТЗ_Порция.Количество() = 0 Тогда
Прервать; // Все записи, удовлетворяющие условию, удалены
КонецЕсли;
НачатьТранзакцию();
Попытка
НаборЗаписей = РегистрыСведений.ШтрихкодыНоменклатуры.СоздатьНаборЗаписей();
НаборЗаписей.РасширенныеРежимыЗамещения = Истина;
НаборЗаписей.ОбменДанными.Загрузка = Истина;
НаборЗаписей.Загрузить(ТЗ_Порция);
НаборЗаписей.Записать(РежимЗамещения.Удаление);
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
КонецЦикла;
КонецПроцедуры
Если режим совместимости конфигурации не позволяет использовать РасширенныеРежимыЗамещения, можно применить классические инженерные подходы в зависимости от структуры регистра:
Номенклатура), запросом получают уникальный список этих объектов, для каждого в цикле устанавливают отбор НаборЗаписей.Отбор.Номенклатура.Установить(...), вызывают метод Прочитать(), программно удаляют из коллекции нужные строки и записывают набор обратно с флагом Замещение = Истина.