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

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

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

Однако на практике при попытке выполнить метод Записать() система часто выдает ошибку вида «Не удалось записать: "Штрихкоды"!» с предварительным выводом сообщений о не заполнено качестве или о том, что штрихкод уже имеет владельца. Рассмотрим подробно, почему возникает эта ошибка, как её исправить и какие методы работы с регистрами сведений являются наименее затратными и безопасными для базы данных.

1. Выясняем причину ошибки при записи набора записей

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


ТаблицаОставляемыхЗаписей = Запрос.Выполнить().Выгрузить(); 

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

При выполнении этого кода система блокирует запись. Причина кроется в прикладном коде модуля набора записей или в подписках на события. Откроем процедуру ПередЗаписью() в модуле набора записей регистра сведений Штрихкоды:


Процедура ПередЗаписью(Отказ, Замещение)
    Если ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;

    Для Каждого ТекущаяЗапись Из ЭтотОбъект Цикл
        Если ТипЗнч(ТекущаяЗапись.Владелец) = Тип("СправочникСсылка.Номенклатура") И Не ЗначениеЗаполнено(ТекущаяЗапись.Качество) Тогда
            Сообщить("Для товара """ + СокрЛП(ТекущаяЗапись.Владелец) + """ со штрихкодом: " + ТекущаяЗапись.ШтрихКод + " не заполнено качество.");
            Отказ = Истина;
        КонецЕсли;
    КонецЦикла;

    Если мНеРазрешитьНеуникальныеШтрихкоды Тогда
        Для Каждого ТекущаяЗапись Из ЭтотОбъект Цикл
            Если ЗначениеЗаполнено(ТекущаяЗапись.ШтрихКод) Тогда
                Запрос = Новый Запрос("
                |ВЫБРАТЬ ПЕРВЫЕ 1
                |    РегШтрихкоды.Владелец КАК Владелец,
                |    РегШтрихкоды.ШтрихКод КАК Штрихкод
                |ИЗ
                |    РегистрСведений.Штрихкоды КАК РегШтрихкоды
                |ГДЕ
                |    РегШтрихкоды.ШтрихКод = &ШтрихКод
                |");
                Запрос.УстановитьПараметр("ШтрихКод", ТекущаяЗапись.ШтрихКод);
                РезультатЗапроса = Запрос.Выполнить();
                Если Не РезультатЗапроса.Пустой() Тогда
                    Выборка = РезультатЗапроса.Выбрать();
                    Выборка.Следующий();
                    Сообщить("Штрихкод: " + Выборка.ШтрихКод + " уже имеет владельца """ + СокрЛП(Выборка.Владелец) + """.");
                    Отказ = Истина;
                КонецЕсли;
            КонецЕсли;
        КонецЦикла;
    КонецЕсли;
КонецПроцедуры

Как мы видим, в алгоритме ПередЗаписью() выполняются жесткие проверки:

  1. Проверяется обязательное заполнение реквизита Качество.
  2. Выполняется запрос к базе данных на наличие в ней записываемого штрихкода. Поскольку в момент записи старые данные из базы еще не устранены, обработчик видит дублирование штрихкодов и взводит флаг Отказ = Истина.

2. Решение проблемы с помощью режима загрузки данных

Обратим внимание на самые первые строки процедуры ПередЗаписью():


Если ОбменДанными.Загрузка Тогда
    Возврат;
КонецЕсли;

Это стандартное свойство объекта 1С, которое сигнализирует платформе о том, что происходит служебный импорт данных или глобальное обновление. Когда свойство ОбменДанными.Загрузка установлено в значение Истина, прикладные проверки в обработчиках событий не выполняются.

Для корректного выполнения записи достаточно включить этот флаг перед вызовом метода Записать():


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

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

3. Почему полная перезапись регистра — это плохая практика

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


// Опасный подход для больших объемов данных!
НаборЗаписей = РегистрыСведений.Штрихкоды.СоздатьНаборЗаписей(); 
НаборЗаписей.Прочитать();
НаборЗаписей.Очистить();
НаборЗаписей.Записать(); // Очистили регистр полностью

НаборЗаписей.Загрузить(ТаблицаОставляемыхЗаписей); 
НаборЗаписей.Записать(); // Записали оставшиеся данные

Проанализируем, к каким последствиям приводит такой метод на физическом уровне базы данных:

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

4. Как правильно и безопасно удалять записи порциями (пакетами)

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

Разберем алгоритм построения такой обработки по шагам:

Шаг 1. Выбираем из базы только те ключевые измерения, которые подлежат удалению. Ограничиваем выборку небольшими порциями (например, по 1000–5000 записей).

Шаг 2. В цикле устанавливаем отбор в наборе записей по конкретным измерениям и записываем пустой набор. Когда у набора записей установлен отбор по всем измерениям и в самом наборе нет строк, вызов метода Записать() удаляет из базы физическую запись, соответствующую отбору.

Посмотрим на пример безопасного порционного удаления:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ ПЕРВЫЕ 1000
    |    Штрихкоды.Штрихкод КАК Штрихкод,
    |    Штрихкоды.Владелец КАК Владелец
    |ИЗ
    |    РегистрСведений.Штрихкоды КАК Штрихкоды
    |ГДЕ
    |    Штрихкоды.Владелец ССЫЛКА Документ.ЧекККМ
    |    И Штрихкоды.Владелец.Дата < &ДатаСвертки";

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

Пока Истина Цикл

    ТаблицаУдаления = Запрос.Выполнить().Выгрузить();
    Если ТаблицаУдаления.Количество() = 0 Тогда
        Прервать; // Все устаревшие записи удалены
    КонецЕсли;

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

КонецЦикла;

5. Оптимизация запроса: избегаем неявных соединений

Обратим внимание на условие запроса при обращении к реквизитам составного типа. Если измерение Владелец имеет составной тип (множество справочников и документов), то обращение через точку вида Штрихкоды.Владелец.Дата заставляет СУБД генерировать сложный SQL-запрос с множеством неявных соединений (LEFT JOIN) ко всем таблицам документов в системе.

Для ускорения работы рекомендуется предварительно составить массив или временную таблицу ссылок на удаляемые документы, а затем передавать их в основной запрос через параметр В (&МассивУдаляемыхОбъектов).

Итоги

Разберем ключевые правила работы при удалении и перезаписи данных в независимых регистрах сведений:

  1. Для обхода прикладных проверок в процедуре ПередЗаписью() используйте установку свойства НаборЗаписей.ОбменДанными.Загрузка = Истина.
  2. Никогда не считывайте и не перезаписывайте весь независимый регистр сведений целиком без крайней необходимости.
  3. Для удаления устаревших данных применяйте порционное удаление через установку точечных отборов в НаборЗаписей.Отбор с последующим вызовом метода Записать().
← На главную