Как правильно и быстро удалить большое количество записей из независимого регистра сведений

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

Работа с большими объемами данных в 1С всегда требует особого подхода, особенно когда речь идет об удалении сотен тысяч или миллионов записей из независимого регистра сведений (РС). Стандартный подход «в лоб» часто приводит к зависанию системы, переполнению лога транзакций или ошибкам связи с сервером вроде POST /e1cib/logForm (особенно когда требуется удаление помеченных на удаление объектов при больших объемах данных). В этой статье мы подробно разберем, как оптимизировать этот процесс, выполнить универсальную очистку базы, избежать типичных ошибок и добиться максимальной производительности.

Анализ проблемы: почему база «висит»

Прежде чем переходить к коду, давайте выясним причину низкой скорости и ошибок. Когда мы пытаемся удалить записи через МенеджерЗаписи в цикле, система выполняет отдельную транзакцию на каждую удаляемую строку. Это создает колоссальную нагрузку на СУБД. Кроме того, если выборка данных для удаления происходит медленно, клиентское приложение может разорвать соединение с сервером по таймауту.

Рассмотрим основные факторы, замедляющие работу:

  1. Отсутствие индексов: Если отбор в запросе идет по реквизиту (например, ДатаСведений), который не является измерением и не индексирован, СУБД выполняет полный перебор таблицы при каждом запросе порции данных.
  2. Лишние операции: Использование метода МенеджерЗаписи.Прочитать() перед удалением в большинстве случаев избыточно, если вы уже получили все ключевые поля в запросе.
  3. Очередь транзакций: Каждое одиночное удаление фиксируется в логе транзакций SQL-сервера, что создает накладные расходы (в таких случаях полезно проанализировать SQL сервер глазами 1С-ника).

Оптимизация логики запроса порциями

Часто разработчики пытаются использовать временные таблицы для исключения уже обработанных записей. Однако, как справедливо замечено в обсуждении, если записи физически удаляются из регистра, они автоматически пропадают из следующей выборки ПЕРВЫЕ 1000. Проанализируем правильную схему запроса в цикле:


ТекстЗапроса = 
"ВЫБРАТЬ ПЕРВЫЕ 1000
|   Регистр.ИД КАК ИД,
|   Регистр.КодТовара КАК КодТовара
|ИЗ
|   РегистрСведений.СведенияОТоварах КАК Регистр
|ГДЕ
|   Регистр.Организация = &Организация
|   И Регистр.ДатаСведений >= &ДатаНачала";

В данном случае нам не нужно левое соединение с ТЗ. Мы просто повторяем запрос до тех пор, пока результат не станет пустым. Каждая итерация будет подхватывать «следующие» 1000 записей, которые стали первыми после удаления предыдущей пачки.

Использование транзакций для ускорения процесса

Для радикального ускорения мы должны группировать операции удаления в блоки. Рассмотрим, как реализовать это правильно. Вместо того чтобы фиксировать каждую запись, мы будем открывать транзакцию на каждые 1000 строк. Разберем этот алгоритм по шагам:


Запрос = Новый Запрос(ТекстЗапроса);
Запрос.УстановитьПараметр("Организация", ВыбраннаяОрганизация);
// Установка других параметров...

Пока Истина Цикл
    Результат = Запрос.Выполнить();
    Если Результат.Пустой() Тогда
        Прервать;
    КонецЕсли;
    
    Выборка = Результат.Выбрать();
    
    НачатьТранзакцию();
    Попытка
        МенеджерЗаписи = РегистрыСведений.СведенияОТоварах.СоздатьМенеджерЗаписи();
        Пока Выборка.Следующий() Цикл
            ЗаполнитьЗначенияСвойств(МенеджерЗаписи, Выборка);
            МенеджерЗаписи.Удалить();
        КонецЦикла;
        ЗафиксироватьТранзакцию();
    Исключение
        ОтменитьТранзакцию();
        ЗаписьЖурналаРегистрации("УдалениеЗаписей", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
        Прервать;
    КонецПопытки;
КонецЦикла;

Важный момент: Менеджер записи лучше создавать один раз перед внутренним циклом и просто перезаполнять его свойства. Это экономит ресурсы на создание объекта в памяти.

Пакетное удаление через НаборЗаписей

Хотя МенеджерЗаписи удобен, работа с НаборЗаписей может быть еще эффективнее. Даже если в регистре много измерений, мы можем удалять записи пачками, устанавливая отбор по ключевым полям. Однако для независимого регистра сведений при удалении конкретных строк по сложному условию (не по всем измерениям) НаборЗаписей требует осторожности: если не установить отбор по всем измерениям, метод Записать(Истина) очистит лишние данные.

Но есть хитрость: если нам нужно очистить все данные по определенному измерению (например, по конкретной Организации), лучше использовать НаборЗаписей с одним отбором по этой организации:


Набор = РегистрыСведений.СведенияОТоварах.СоздатьНаборЗаписей();
Набор.Отбор.Организация.Установить(ВыбраннаяОрганизация);
Набор.Записать(Истина); // Запись пустого набора удалит все записи по этой организации

Технические нюансы и рекомендации

Рассмотрим дополнительные методы, которые помогут избежать «падения» базы при очень больших объемах (миллионы строк):

Выясним причину, по которой иногда советуют метод «удалить всё и вернуть нужное». Если вам требуется удалить более 80% записей регистра, зачастую быстрее будет выгрузить оставшиеся 20% во временную таблицу или массив, полностью очистить регистр методом Набор.Записать(Истина) без отборов, а затем загрузить сохраненные данные обратно. Это работает в разы быстрее, чем выборочное удаление миллионов строк.

Проанализировав ситуацию, можно сделать вывод: ключ к успеху кроется в сочетании правильной индексации, группировки удалений в транзакции по 500-1000 строк, использовании многопоточного режима и выполнении кода на стороне сервера в контексте фонового задания.

← На главную