Работа с регистрами сведений, содержащими десятки миллионов записей (например, хранилища акцизных марок или кодов маркировки), накладывает серьезные ограничения на производительность. Стандартные методы удаления, которые отлично работают на малых объемах, могут выполняться часами или даже сутками, парализуя работу системы. Разберем детально, как оптимизировать процесс очистки подчиненного регистра сведений, на примере задачи удаления выбывших марок за период старше полугода.
Проанализируем исходный код, который часто становится причиной низкой производительности. Одной из главных проблем является использование вложенных циклов при обработке набора записей. Рассмотрим типичную ошибку: программист выбирает регистраторы запросом, а затем внутри цикла по регистраторам делает еще один цикл по детальным записям запроса и вложенный цикл по самому набору записей.
Если в одном документе (регистраторе) содержится 20 000 марок, то количество итераций сравнения внутри одного набора превращается в 20 000 * 20 000 = 400 000 000 операций. Это классическая квадратичная сложность, которая «съедает» процессорное время. Выясним, как этого избежать.
Для оптимизации процесса удаления нам необходимо прочитать набор записей один раз и пройтись по нему в обратном порядке, удаляя ненужные строки. Обратный порядок критически важен: при удалении элемента из коллекции индексы последующих элементов смещаются. Если идти с начала, мы либо пропустим элементы, либо получим ошибку индексации. Разберем правильный подход по шагам:
НаборЗаписей.Количество() - 1 до 0.Рассмотрим пример оптимизированного кода для удаления конкретных записей из набора:
Процедура ОчиститьЗаписиВЗависимомРегистреСведений(Регистратор, СписокУдаляемыхМарок)
Набор = РегистрыСведений.алкХранилищеАкцизныхМарок.СоздатьНаборЗаписей();
Набор.Отбор.Регистратор.Установить(Регистратор);
Набор.Прочитать();
// Используем обратный цикл по индексу
Для Индекс = -(Набор.Количество() - 1) По 0 Цикл
Запись = Набор[-Индекс];
// Если марка входит в список на удаление
Если СписокУдаляемыхМарок.Найти(Запись.Марка) <> Неопределено Тогда
Набор.Удалить(-Индекс);
КонецЕсли;
КонецЦикла;
// Отключаем лишние проверки для ускорения записи
Набор.ОбменДанными.Загрузка = Истина;
Набор.Записать();
КонецПроцедуры
Когда в регистре более 30 миллионов записей, выполнение операции в один поток неэффективно. Современные серверы обладают многоядерными процессорами, и наша задача — утилизировать эти ресурсы. Мы можем распределить удаление данных по разным регистраторам между несколькими фоновыми заданиями.
Проанализируем ситуацию с блокировками. 1С:Предприятие при работе с подчиненными регистрами накладывает блокировки по регистратору. Это означает, что несколько потоков могут одновременно обрабатывать разные документы, не мешая друг другу. Однако стоит помнить об эскалации блокировок: если один поток попытается заблокировать слишком много записей, СУБД может заблокировать всю таблицу целиком. Поэтому рекомендуется ограничивать объем данных в одной транзакции (например, обрабатывать по 10-50 регистраторов в одном задании).
Для реализации многопоточности можно использовать типовой механизм ФоновыеЗадания.Выполнить() или специализированные инструменты разработчика, которые умеют дробить выборку на порции и запускать обработку в заданное количество потоков.
Если штатные средства 1С не справляются, а задача разовая (например, глобальная зачистка старых данных), рассмотрим возможность использования прямых SQL-запросов. Несмотря на то, что это нарушает лицензионное соглашение 1С (в части изменения данных вне платформы), в критических ситуациях системные администраторы прибегают к этому методу.
Операция DELETE FROM на уровне СУБД выполняется значительно быстрее, так как она не тратит ресурсы на сериализацию объектов 1С, работу метаданных и проверку прав доступа. Разберем ситуацию: нам нужно удалить все записи, где ОтметкаВыбытия = ИСТИНА и период старше определенной даты.
Пример логики SQL-запроса:
-- Удаление записей напрямую из таблицы регистра
DELETE FROM _InfoRg33266 -- Имя таблицы в БД
WHERE _Fld33267 = 1 -- Поле ОтметкаВыбытия (Истина)
AND _Period < '2020-06-01' -- Условие по периоду
Важное предупреждение: При массовом удалении через SQL журнал транзакций (LDF-файл) может мгновенно вырасти до огромных размеров. Перед выполнением таких операций рекомендуется перевести базу данных в модель восстановления Simple. Также эффективнее удалять данные порциями (пакетами) в цикле с помощью конструкции DELETE TOP (10000), чтобы не блокировать таблицу на длительное время.
Для того чтобы проблема не повторялась, проанализируем структуру регистра. Использование длинных строк (150–160 символов) в качестве измерений — это крайне тяжелое решение для СУБД. Индексы по таким полям занимают в разы больше места, чем сами данные.
Рассмотрим способы архитектурной оптимизации:
Число или Строка(32)). Поиск и удаление по хешу происходят значительно быстрее.ШтрихкодыУпаковок. В регистре храните не саму строку, а ссылку на элемент справочника. Это уменьшит размер каждой записи в регистре и ускорит индексацию.Выясним, какой метод подходит вам больше всего. Если нужно «здесь и сейчас» очистить базу — исправляйте вложенные циклы и запускайте обработку в 5-10 потоков. Если база продолжит расти — планируйте рефакторинг с переходом на ссылочный тип хранения марок и хеширование.