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

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

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

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

Причина возникновения проблемы

Рассмотрим ситуацию: у нас есть регистр сведений, где поле Документ является реквизитом, а не измерением, и индексация для него отключена. С точки зрения СУБД (SQL Server, PostgreSQL), поиск по такому полю вызывает операцию Full Table Scan. Это означает, что серверу приходится прочитать каждую запись в таблице, чтобы найти нужные соответствия. Если удаление происходит в цикле по большому списку документов, производительность системы падает до критических значений.

Метод 1. Использование Менеджера записи и Ключа записи

Если нам нужно удалить конкретные строки, а стандартный отбор в НаборЗаписей недоступен, мы можем воспользоваться комбинацией запроса и МенеджераЗаписи. Это точечный метод, который позволяет работать с конкретной строкой, зная значения её измерений.

Разберем алгоритм действий:

  1. Выполняем запрос к регистру сведений, устанавливая условие по нашему неиндексированному полю Документ.
  2. В результате запроса обязательно выбираем значения всех измерений регистра.
  3. В цикле обхода результата создаем КлючЗаписи или напрямую заполняем значения измерений в МенеджереЗаписи.
  4. Вызываем метод Удалить().

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


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

Запрос.УстановитьПараметр("Документ", СсылкаНаДокумент);
Выборка = Запрос.Выполнить().Выбрать();

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

Этот способ хорош тем, что он абсолютно безопасен и следует логике платформы. Однако для массового удаления он может быть медленным, так как каждый вызов Удалить() инициирует отдельное обращение к базе данных.

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

Чтобы ускорить процесс, проанализируем возможность использования НаборЗаписей. Хотя мы не можем установить отбор по полю Документ, мы можем установить отбор по измерениям, значения которых мы предварительно получили запросом. Это значительно эффективнее, так как 1С формирует более производительные SQL-команды для наборов.

Рассмотрим по шагам, как реализовать пакетную обработку:

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

Важный нюанс: если измерений много, установка отбора по всем сразу позволит однозначно идентифицировать запись. Если установить отбор только по части измерений, метод Записать(Истина) удалит все записи, попадающие под этот частичный фильтр. Будьте крайне осторожны!

Метод 3. Оптимизация структуры: Свойство Ведущее и Индексация

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

Вариант А: Установка индекса. В конфигураторе для реквизита Документ можно установить свойство Индексировать. Это создаст в базе данных индекс по этому полю. После этого поиск и удаление станут мгновенными, но увеличится время записи в регистр и объем базы данных.

Вариант Б: Использование свойства "Ведущее". Если поле Документ является измерением, мы можем установить у него флаг Ведущее. Это магическое свойство 1С: при удалении объекта-владельца (самого документа), платформа автоматически очистит все записи в этом регистре, где данный документ указан в этом измерении. Это самый правильный путь, не требующий написания программного кода для очистки.

Метод 4. Прямые SQL-запросы (для продвинутых)

В некоторых случаях, когда нужно очистить миллионы записей в неиндексированном регистре, стандартные средства 1С могут работать часы. Рассмотрим вариант прямого обращения к СУБД, который упоминался в обсуждении (Сообщение 5).

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


-- Пример для MS SQL Server
DELETE FROM _InfoReg1234 -- имя таблицы в БД
WHERE _Fld5678_RRRef = 0x80DB000C29E6056911E39B7F1C934789 -- UUID документа в бинарном виде

Если вы решитесь на этот шаг, обязательно выполните следующие действия:

Метод 5. Использование временных таблиц в запросе для массовой выборки

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

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


// Создаем таблицу значений с документами
ТЗДокументы = Новый ТаблицаЗначений;
ТЗДокументы.Колонки.Добавить("Документ", Новый ОписаниеТипов("ДокументСсылка.ТвойТип"));
// Заполняем ТЗ...

МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос.Текст = "ВЫБРАТЬ Документ ПОМЕСТИТЬ ВТ_Документы ИЗ &ТЗ КАК ТЗ";
Запрос.УстановитьПараметр("ТЗ", ТЗДокументы);
Запрос.Выполнить();

// Теперь соединяем ВТ с регистром
Запрос.Текст = 
    "ВЫБРАТЬ
    |   Регистр.Измерение1,
    |   Регистр.Измерение2
    |ИЗ
    |   РегистрСведений.ТвойРегистр КАК Регистр
    |ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Документы КАК ВТ
    |   ПО Регистр.Документ = ВТ.Документ";

Выборка = Запрос.Выполнить().Выбрать();
// Далее удаляем через НаборЗаписей или МенеджерЗаписи порциями

Рекомендации по работе с транзакциями

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

Разделите общее количество записей на пакеты (например, по 1000 или 5000 штук). Каждый пакет оборачивайте в одну транзакцию:


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

Такой подход позволит значительно ускорить процесс за счет группировки операций записи в одну транзакцию БД, при этом не переполняя лог транзакций и не блокируя таблицу на слишком долгое время.

Заключение

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

← На главную