При разработке и сопровождении конфигураций 1С разработчики нередко сталкиваются с необходимостью удалить устаревшие или ошибочные записи регистра сведений за определенный интервал дат. Первая интуитивная попытка часто выглядит так: создать набор записей, наложить отбор на дату начала и окончания интервала, а затем вызвать метод записи пустого набора. Однако такой подход приводит к ошибке или не дает желаемого результата. Выясним причину такого поведения платформы и подробно рассмотрим корректные и безопасные способы решения этой задачи.
Проанализируем архитектурное устройство объекта РегистрСведенийНаборЗаписей. В платформе «1С:Предприятие» механизм отбора набора записей имеет строгие технические ограничения:
Отбор набора записей поддерживает исключительно вид сравнения Равно. Платформа физически не позволяет задать интервалы вида «больше или равно» или «меньше или равно». Попытка дважды вызвать метод Установить() для одного и того же поля даты приведет лишь к тому, что второе значение перезапишет первое, и отбор останется на точное равенство последней переданной дате.Период, если регистр является периодическим). Если дата хранится в ресурсе или обычном реквизите (например, ДатаКоллизии), установить по ней отбор на уровне набора записей в принципе невозможно — попытка вызовет исключение времени выполнения.Регистратор.Если же создать набор записей вовсе без установки отборов и вызвать метод Записать(), платформа полностью очистит всю таблицу регистра сведений в базе данных, что на рабочей базе приведет к катастрофической потере информации и блокировке системных индексов СУБД. Поэтому для очистки регистра за диапазон дат применяются другие подходы.
Рассмотрим наиболее универсальный и распространенный способ: предварительно получить список удаляемых записей с помощью запроса, а затем обойти результат в цикле, удаляя строки с помощью объекта РегистрСведенийМенеджерЗаписи.
Разберем по шагам реализацию данного алгоритма:
РегистрСведенийМенеджерЗаписи, заполняем его ключевые поля и вызываем метод Удалить().ОбменДанными.Загрузка = Истина, чтобы не перегружать СУБД и не регистрировать лишние изменения для планов обмена.ОбработкаПрерыванияПользователя().Посмотрим на практический пример кода:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Коллизии.ДатаКоллизии КАК ДатаКоллизии,
| Коллизии.Узел КАК Узел,
| Коллизии.Объект КАК Объект
|ИЗ
| РегистрСведений.КоллизииПриОбмене КАК Коллизии
|ГДЕ
| Коллизии.ДатаКоллизии МЕЖДУ &ДатаОт И &ДатаДо";
Запрос.УстановитьПараметр("ДатаОт", ДатаОт);
Запрос.УстановитьПараметр("ДатаДо", ДатаДо);
Выборка = Запрос.Выполнить().Выбрать();
Счетчик = 0;
НачатьТранзакцию();
Пока Выборка.Следующий() Цикл
ОбработкаПрерыванияПользователя();
МЗ = РегистрыСведений.КоллизииПриОбмене.СоздатьМенеджерЗаписи();
ЗаполнитьЗначенияСвойств(МЗ, Выборка);
// Отключаем проверки модулей и регистрацию изменений в планах обмена
МЗ.ОбменДанными.Загрузка = Истина;
МЗ.Удалить();
Счетчик = Счетчик + 1;
Если Счетчик % 1000 = 0 Тогда
ЗафиксироватьТранзакцию();
НачатьТранзакцию();
КонецЕсли;
КонецЦикла;
Если Счетчик % 1000 <> 0 Тогда
ЗафиксироватьТранзакцию();
Иначе
ОтменитьТранзакцию();
КонецЕсли;
Рассмотрим альтернативный способ, который работает быстрее менеджера записи на больших объемах данных. Менеджер записи является программной оберткой над набором записей с отбором по всем ключевым полям и при удалении каждой строки выполняет отдельные системные обращения.
Если в регистре есть основные разрезы (например, узлы обмена, организации или контрагенты), мы можем запросом выбрать уникальные комбинации измерений и дат, а затем очищать данные целыми наборами. На пустой набор записей накладывается отбор по конкретным значениям измерений, после чего вызывается метод Записать(). На уровне СУБД такая операция выполняется пакетом и значительно снижает нагрузку на дисковую подсистему.
Посмотрим на пример удаления точечными наборами:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| Коллизии.Период КАК Период,
| Коллизии.Узел КАК Узел
|ИЗ
| РегистрСведений.КоллизииПриОбмене КАК Коллизии
|ГДЕ
| Коллизии.Период МЕЖДУ &ДатаОт И &ДатаДо";
Запрос.УстановитьПараметр("ДатаОт", ДатаОт);
Запрос.УстановитьПараметр("ДатаДо", ДатаДо);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
НЗ = РегистрыСведений.КоллизииПриОбмене.СоздатьНаборЗаписей();
НЗ.Отбор.Период.Установить(Выборка.Период);
НЗ.Отбор.Узел.Установить(Выборка.Узел);
НЗ.ОбменДанными.Загрузка = Истина;
// Запись пустого набора удаляет все записи, подходящие под этот отбор
НЗ.Записать();
КонецЦикла;
В обсуждении также предлагается вариант выгрузить все данные, которые не входят в удаляемый интервал, а затем перезаписать регистр целиком. Разберем этот вариант:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| *
|ИЗ
| РегистрСведений.КоллизииПриОбмене КАК Коллизии
|ГДЕ
| НЕ Коллизии.ДатаКоллизии МЕЖДУ &ДатаОт И &ДатаДо";
Запрос.УстановитьПараметр("ДатаОт", ДатаОт);
Запрос.УстановитьПараметр("ДатаДо", ДатаДо);
ТаблицаОстающихсяДанных = Запрос.Выполнить().Выгрузить();
НаборЗаписей = РегистрыСведений.КоллизииПриОбмене.СоздатьНаборЗаписей();
НаборЗаписей.Загрузить(ТаблицаОстающихсяДанных);
НаборЗаписей.ОбменДанными.Загрузка = Истина;
НаборЗаписей.Записать();
Важное предостережение: этот способ допустим исключительно для компактных справочных регистров, содержащих до нескольких десятков тысяч строк. Если в регистре миллионы записей, а удалить требуется всего несколько дней, выгрузка таблицы в оперативную память сервера (Выгрузить()) приведет к исчерпанию оперативной памяти, жесткому свопу и падению рабочего процесса сервера «1С:Предприятия».
Если перед нами стоит задача очистить миллионы строк из высоконагруженного регистра, проанализируем следующие правила безопасной очистки:
ВЫБРАТЬ ПЕРВЫЕ N с сортировкой по первичному ключу регистра, повторяя удаление порциями (например, по 5 000 строк) в цикле до тех пор, пока запрос возвращает строки.ОбменДанными.Загрузка = Истина. Это отключает обработчики событий ПередЗаписью и ПриЗаписи модуля набора записей, исключая каскадную валидацию и неоправданный расход ресурсов процессора.