Как быстро оставить только записи с минимальной ценой в большом регистре сведений

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

Работа с большими объемами данных в 1С:Предприятие всегда требует особого подхода. Когда в независимом непериодическом регистре сведений накапливается 50 миллионов записей, стандартные методы «удаления в лоб» перестают работать — система может зависнуть на часы или вызвать переполнение лога транзакций SQL-сервера. В данной статье мы подробно разберем, как решить задачу фильтрации такого регистра, чтобы оставить в нем только уникальные связки КодПроизводитель с минимальной ценой.

Анализ исходной ситуации

Проанализируем входные данные: у нас есть независимый регистр сведений с измерениями Код, Производитель и ресурсом Цена. Наша цель — избавиться от избыточности, оставив для каждой пары ключ-производитель только одну запись с наименьшей стоимостью. При объеме в 50 млн записей важно понимать, какой процент данных подлежит удалению. Если это 80% и более (как в нашем случае), то гораздо быстрее не удалять лишнее, а пересоздать данные «с нуля».

Метод 1: Полная перезапись через запрос и временную таблицу

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

  1. Получение данных: Выполним запрос к регистру с группировкой по измерениям и выбором минимального ресурса.
  2. Очистка регистра: Полностью очистим регистр, используя пустой набор записей.
  3. Запись данных: Загрузим отобранные данные обратно.

Рассмотрим пример кода для реализации этого метода:


// 1. Получаем нужные данные запросом
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Цены.Код КАК Код,
    |	Цены.Производитель КАК Производитель,
    |	МИНИМУМ(Цены.Цена) КАК Цена
    |ИЗ
    |	РегистрСведений.ЦеныПоставщиков КАК Цены
    |ГРУППИРОВАТЬ ПО
    |	Цены.Код,
    |	Цены.Производитель";

РезультатЗапроса = Запрос.Выполнить();
ТаблицаЧистыхДанных = РезультатЗапроса.Выгрузить();

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

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

Важный нюанс: метод Загрузить() работает значительно быстрее, чем Добавить() в цикле, так как платформа оптимизирует передачу таблицы значений во внутренние структуры набора. Однако при 10–15 млн оставшихся записей попытка записать их одним набором может привести к ошибке памяти. В этом случае мы рекомендуем разбивать таблицу ТаблицаЧистыхДанных на порции по 100 000 – 500 000 строк.

Метод 2: Использование вспомогательного регистра (Метод подмены)

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

  1. Создадим в конфигураторе новый регистр сведений ЦеныПоставщиков_Буфер с идентичной структурой.
  2. Заполним его данными из основного регистра с помощью запроса (как в методе 1).
  3. В момент технологического окна удалим старый регистр и переименуем новый. Либо очистим основной и перельем в него данные из буфера.

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

Метод 3: Оптимизация на уровне СУБД (SQL)

Для программистов, имеющих доступ к MS SQL Server или PostgreSQL, существует «реактивный» способ. Рассмотрим его подробнее. Операции на уровне SQL позволяют избежать накладных расходов сервера приложений 1С.

Алгоритм действий:

  1. Выясним имя таблицы регистра в базе данных с помощью функции ПолучитьСтруктуруХраненияБазыДанных().
  2. Создадим временную таблицу в SQL и вставим в нее результат запроса SELECT ... MIN(Цена) ... GROUP BY.
  3. Используем команду TRUNCATE TABLE для мгновенной очистки исходного регистра. Это работает на порядки быстрее DELETE, так как не пишет каждую строку в лог транзакций.
  4. Перенесем данные из временной таблицы в основную командой INSERT INTO ... SELECT.

Внимание: при работе с SQL напрямую индексы могут замедлять вставку. Проанализируем совет из практики: перед массовой заливкой 20 млн строк стоит отключить (Drop) индексы в SQL, а после завершения — пересоздать их. Это сэкономит до 50% времени.

Метод 4: Пакетная обработка в транзакции (Средствами 1С)

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

При использовании НаборЗаписей крайне важно не устанавливать отборы слишком часто. Если мы удаляем записи по условию, лучше всего делать это транзакциями. Посмотрим на пример оптимизированного цикла:


// Пример пакетной записи (сообщение 18 обсуждения)
РазмерПорции = 10000;
Счетчик = 0;

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

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

Технические нюансы и безопасность

Выясним, на что еще нужно обратить внимание при работе с такими объемами:

Итог совместной работы: Мы разобрали несколько сценариев. Самый эффективный для 50 млн записей — это выборка уникальных данных во временную таблицу (или ТЗ), очистка регистра методом Записать(Истина) и последующая порционная загрузка через Набор.Загрузить(). Это гарантирует высокую скорость и стабильность системы.

← На главную