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

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

При разработке и сопровождении конфигураций 1С разработчики нередко сталкиваются с необходимостью удалить устаревшие или ошибочные записи регистра сведений за определенный интервал дат. Первая интуитивная попытка часто выглядит так: создать набор записей, наложить отбор на дату начала и окончания интервала, а затем вызвать метод записи пустого набора. Однако такой подход приводит к ошибке или не дает желаемого результата. Выясним причину такого поведения платформы и подробно рассмотрим корректные и безопасные способы решения этой задачи.

Почему нельзя очистить период через отбор набора записей?

Проанализируем архитектурное устройство объекта РегистрСведенийНаборЗаписей. В платформе «1С:Предприятие» механизм отбора набора записей имеет строгие технические ограничения:

  1. Вид сравнения: свойство Отбор набора записей поддерживает исключительно вид сравнения Равно. Платформа физически не позволяет задать интервалы вида «больше или равно» или «меньше или равно». Попытка дважды вызвать метод Установить() для одного и того же поля даты приведет лишь к тому, что второе значение перезапишет первое, и отбор останется на точное равенство последней переданной дате.
  2. Состав полей отбора: отбор в наборе записей регистра сведений можно устанавливать только по измерениям регистра (а также по стандартному реквизиту Период, если регистр является периодическим). Если дата хранится в ресурсе или обычном реквизите (например, ДатаКоллизии), установить по ней отбор на уровне набора записей в принципе невозможно — попытка вызовет исключение времени выполнения.
  3. Режим подчинения регистратору: если регистр сведений подчинен регистратору, отбор набора записей вообще возможен исключительно по полю Регистратор.

Если же создать набор записей вовсе без установки отборов и вызвать метод Записать(), платформа полностью очистит всю таблицу регистра сведений в базе данных, что на рабочей базе приведет к катастрофической потере информации и блокировке системных индексов СУБД. Поэтому для очистки регистра за диапазон дат применяются другие подходы.

Способ 1. Выборка данных запросом и удаление через МенеджерЗаписи

Рассмотрим наиболее универсальный и распространенный способ: предварительно получить список удаляемых записей с помощью запроса, а затем обойти результат в цикле, удаляя строки с помощью объекта РегистрСведенийМенеджерЗаписи.

Разберем по шагам реализацию данного алгоритма:

  1. Формируем запрос, выбирающий ключевые поля (все измерения и дату) за требуемый интервал дат.
  2. В цикле по выборке инициализируем РегистрСведенийМенеджерЗаписи, заполняем его ключевые поля и вызываем метод Удалить().
  3. Обязательно отключаем бизнес-логику и подписки на события с помощью установки свойства ОбменДанными.Загрузка = Истина, чтобы не перегружать СУБД и не регистрировать лишние изменения для планов обмена.
  4. Для предотвращения зависания интерфейса используем метод ОбработкаПрерыванияПользователя().
  5. Чтобы избежать создания сотен тысяч отдельных транзакций в СУБД (что приводит к резкому росту журнала транзакций LDF), группируем удаление в явные системные транзакции порциями по 1 000 – 5 000 записей.

Посмотрим на практический пример кода:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Коллизии.ДатаКоллизии КАК ДатаКоллизии,
    |	Коллизии.Узел КАК Узел,
    |	Коллизии.Объект КАК Объект
    |ИЗ
    |	РегистрСведений.КоллизииПриОбмене КАК Коллизии
    |ГДЕ
    |	Коллизии.ДатаКоллизии МЕЖДУ &ДатаОт И &ДатаДо";

Запрос.УстановитьПараметр("ДатаОт", ДатаОт);
Запрос.УстановитьПараметр("ДатаДо", ДатаДо);

Выборка = Запрос.Выполнить().Выбрать();

Счетчик = 0;
НачатьТранзакцию();

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

Если Счетчик % 1000 <> 0 Тогда
    ЗафиксироватьТранзакцию();
Иначе
    ОтменитьТранзакцию();
КонецЕсли;

Способ 2. Удаление наборами записей по уникальным комбинациям измерений

Рассмотрим альтернативный способ, который работает быстрее менеджера записи на больших объемах данных. Менеджер записи является программной оберткой над набором записей с отбором по всем ключевым полям и при удалении каждой строки выполняет отдельные системные обращения.

Если в регистре есть основные разрезы (например, узлы обмена, организации или контрагенты), мы можем запросом выбрать уникальные комбинации измерений и дат, а затем очищать данные целыми наборами. На пустой набор записей накладывается отбор по конкретным значениям измерений, после чего вызывается метод Записать(). На уровне СУБД такая операция выполняется пакетом и значительно снижает нагрузку на дисковую подсистему.

Посмотрим на пример удаления точечными наборами:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ РАЗЛИЧНЫЕ
    |	Коллизии.Период КАК Период,
    |	Коллизии.Узел КАК Узел
    |ИЗ
    |	РегистрСведений.КоллизииПриОбмене КАК Коллизии
    |ГДЕ
    |	Коллизии.Период МЕЖДУ &ДатаОт И &ДатаДо";

Запрос.УстановитьПараметр("ДатаОт", ДатаОт);
Запрос.УстановитьПараметр("ДатаДо", ДатаДо);

Выборка = Запрос.Выполнить().Выбрать();

Пока Выборка.Следующий() Цикл
    
    НЗ = РегистрыСведений.КоллизииПриОбмене.СоздатьНаборЗаписей();
    НЗ.Отбор.Период.Установить(Выборка.Период);
    НЗ.Отбор.Узел.Установить(Выборка.Узел);
    
    НЗ.ОбменДанными.Загрузка = Истина;
    // Запись пустого набора удаляет все записи, подходящие под этот отбор
    НЗ.Записать();
    
КонецЦикла;

Способ 3. Перезапись сохраняемых данных (для небольших регистров)

В обсуждении также предлагается вариант выгрузить все данные, которые не входят в удаляемый интервал, а затем перезаписать регистр целиком. Разберем этот вариант:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	*
    |ИЗ
    |	РегистрСведений.КоллизииПриОбмене КАК Коллизии
    |ГДЕ
    |	НЕ Коллизии.ДатаКоллизии МЕЖДУ &ДатаОт И &ДатаДо";

Запрос.УстановитьПараметр("ДатаОт", ДатаОт);
Запрос.УстановитьПараметр("ДатаДо", ДатаДо);

ТаблицаОстающихсяДанных = Запрос.Выполнить().Выгрузить();

НаборЗаписей = РегистрыСведений.КоллизииПриОбмене.СоздатьНаборЗаписей();
НаборЗаписей.Загрузить(ТаблицаОстающихсяДанных);
НаборЗаписей.ОбменДанными.Загрузка = Истина;
НаборЗаписей.Записать();

Важное предостережение: этот способ допустим исключительно для компактных справочных регистров, содержащих до нескольких десятков тысяч строк. Если в регистре миллионы записей, а удалить требуется всего несколько дней, выгрузка таблицы в оперативную память сервера (Выгрузить()) приведет к исчерпанию оперативной памяти, жесткому свопу и падению рабочего процесса сервера «1С:Предприятия».

Оптимизация при удалении огромных массивов данных

Если перед нами стоит задача очистить миллионы строк из высоконагруженного регистра, проанализируем следующие правила безопасной очистки:

  1. Выборка порциями: не выгружайте всю выборку в память сразу. Используйте конструкцию ВЫБРАТЬ ПЕРВЫЕ N с сортировкой по первичному ключу регистра, повторяя удаление порциями (например, по 5 000 строк) в цикле до тех пор, пока запрос возвращает строки.
  2. Использование флага загрузки: всегда устанавливайте ОбменДанными.Загрузка = Истина. Это отключает обработчики событий ПередЗаписью и ПриЗаписи модуля набора записей, исключая каскадную валидацию и неоправданный расход ресурсов процессора.
  3. Блокировки СУБД: проводите массовое удаление в технологические окна, когда активность пользователей минимальна, чтобы избежать взаимных блокировок (Deadlocks) на таблицах регистра.
← На главную