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

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

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

В данной статье мы разберем, как обойти это ограничение, рассмотрим программные способы удаления таких записей (включая сценарии, когда требуется очистка записей регистров по битым документам) и проанализируем методы оптимизации для работы с большими объемами данных — для этого есть диагностика и удаление битых ссылок в регистрах.

Метод 1. Использование запроса и объекта МенеджерЗаписи

Если вам не хочется писать код, можно использовать инструменты для универсальной очистки базы — есть универсальная обработка поиска и удаления лишних данных.

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

Проанализируем ситуацию: нам нужно удалить записи, где реквизит ДатаЗаписи меньше определенного значения. Также может потребоваться групповая корректировка записей регистров (поможет безопасное удаление битых ссылок в объектах базы). Разберем по шагам, как это реализовать:

  1. Создадим запрос к таблице регистра сведений.
  2. В условии запроса (ГДЕ) укажем фильтр по нашему реквизиту.
  3. В качестве выбираемых полей выберем все измерения регистра. Это критически важно, так как менеджер записи может найти конкретную строку в базе только при наличии полного набора ключевых полей.
  4. Обойдем результат запроса в цикле и удалим записи.

Рассмотрим пример кода:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   МойРегистр.Измерение1,
    |   МойРегистр.Измерение2,
    |   МойРегистр.Измерение3,
    |   МойРегистр.Измерение4,
    |   МойРегистр.Измерение5
    |ИЗ
    |   РегистрСведений.МойРегистр КАК МойРегистр
    |ГДЕ
    |   МойРегистр.ДатаЗаписи < &ГраницаДаты";

Запрос.УстановитьПараметр("ГраницаДаты", Объект.ДатаОчистки);
Выборка = Запрос.Выполнить().Выбрать();

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

Важный момент: использование метода Прочитать() перед Удалить() гарантирует, что мы не попытаемся удалить уже несуществующую запись, что может случиться в многопользовательской среде.

Метод 2. Пакетное удаление через НаборЗаписей (Оптимизация)

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

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

Рассмотрим алгоритм пакетного удаления:


Выборка = Запрос.Выполнить().Выбрать();
НаборЗаписей = РегистрыСведений.МойРегистр.СоздатьНаборЗаписей();

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

Метод 3. Подготовка структуры данных (Индексирование)

Чтобы наш запрос в регламентном задании не «вешал» систему при поиске записей по реквизиту ДатаЗаписи, проанализируем настройки метаданных. Если в регистре миллионы записей, поиск по неиндексированному полю приведет к полному сканированию таблицы (Full Table Scan).

Рассмотрим рекомендации по настройке:

  1. Откройте конфигуратор и найдите ваш РегистрСведений.
  2. Перейдите на закладку Данные.
  3. Выберите нужный реквизит (например, ДатаЗаписи) и откройте его свойства.
  4. Установите свойство Индексировать в значение "Индексировать" или "Индексировать с дополнительным упорядочиванием".

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

Метод 4. Борьба с блокировками в регламентных заданиях

При выполнении массового удаления в работающей базе данных мы неизбежно столкнемся с проблемой блокировок. Если мы попытаемся удалить 100 000 записей в одной транзакции, другие пользователи не смогут записывать данные в этот регистр до завершения операции.

Посмотрим, как правильно организовать удаление в регламентном задании:

Пример структуры кода для регламентного задания:


Пока Истина Цикл
    Запрос.Текст = "ВЫБРАТЬ ПЕРВЫЕ 1000 Измерения... ГДЕ Дата < &Дата";
    Результат = Запрос.Выполнить();
    Если Результат.Пустой() Тогда Прервать; КонецЕсли;
    
    Выборка = Результат.Выбрать();
    НачатьТранзакцию();
    Пока Выборка.Следующий() Цикл
        // Здесь логика удаления из Метода 1 или 2
    КонецЦикла;
    ЗафиксироватьТранзакцию();
    
    // Пауза 1-2 секунды, чтобы дать поработать другим процессам
    #Если Сервер Тогда
        // Код для задержки
    #КонецЕсли
КонецЦикла;

Архитектурный совет

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

Важно: Не добавляйте лишние измерения (как предлагалось в некоторых сообщениях на форуме) только ради удобства удаления. Каждое новое измерение увеличивает размер индекса и замедляет запись новых данных. Используйте комбинацию индексированного реквизита и пакетного удаления через запрос — это наиболее сбалансированное решение.

← На главную