Как оптимизировать удаление записей из подчиненного регистра сведений при больших объемах данных?

Программист 1С v8.3 (Обычные формы) Управленческий учет Торговля и дистрибуция
← На главную

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

Анализ и устранение алгоритмических ошибок

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

Если в одном документе (регистраторе) содержится 20 000 марок, то количество итераций сравнения внутри одного набора превращается в 20 000 * 20 000 = 400 000 000 операций. Это классическая квадратичная сложность, которая «съедает» процессорное время. Выясним, как этого избежать.

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

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

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


Процедура ОчиститьЗаписиВЗависимомРегистреСведений(Регистратор, СписокУдаляемыхМарок)
    Набор = РегистрыСведений.алкХранилищеАкцизныхМарок.СоздатьНаборЗаписей();
    Набор.Отбор.Регистратор.Установить(Регистратор);
    Набор.Прочитать();
    
    // Используем обратный цикл по индексу
    Для Индекс = -(Набор.Количество() - 1) По 0 Цикл
        Запись = Набор[-Индекс];
        // Если марка входит в список на удаление
        Если СписокУдаляемыхМарок.Найти(Запись.Марка) <> Неопределено Тогда
            Набор.Удалить(-Индекс);
        КонецЕсли;
    КонецЦикла;
    
    // Отключаем лишние проверки для ускорения записи
    Набор.ОбменДанными.Загрузка = Истина;
    Набор.Записать();
КонецПроцедуры

Использование многопоточности (фоновые задания)

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

Проанализируем ситуацию с блокировками. 1С:Предприятие при работе с подчиненными регистрами накладывает блокировки по регистратору. Это означает, что несколько потоков могут одновременно обрабатывать разные документы, не мешая друг другу. Однако стоит помнить об эскалации блокировок: если один поток попытается заблокировать слишком много записей, СУБД может заблокировать всю таблицу целиком. Поэтому рекомендуется ограничивать объем данных в одной транзакции (например, обрабатывать по 10-50 регистраторов в одном задании).

Для реализации многопоточности можно использовать типовой механизм ФоновыеЗадания.Выполнить() или специализированные инструменты разработчика, которые умеют дробить выборку на порции и запускать обработку в заданное количество потоков.

Оптимизация через прямые SQL-запросы

Если штатные средства 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 символов) в качестве измерений — это крайне тяжелое решение для СУБД. Индексы по таким полям занимают в разы больше места, чем сами данные.

Рассмотрим способы архитектурной оптимизации:

  1. Хеширование: Добавьте в регистр измерение с хеш-суммой марки (например, MD5, тип Число или Строка(32)). Поиск и удаление по хешу происходят значительно быстрее.
  2. Справочник вместо строки: Создайте справочник ШтрихкодыУпаковок. В регистре храните не саму строку, а ссылку на элемент справочника. Это уменьшит размер каждой записи в регистре и ускорит индексацию.
  3. Независимый регистр: Если бизнес-логика позволяет, откажитесь от подчинения регистратору. Удаление из независимого регистра сведений происходит гораздо быстрее, так как не требуется контроль целостности ссылок на документы.
  4. Регистр накопления: В некоторых случаях (например, для учета только актуальных остатков марок) эффективнее использовать регистр накопления. В нем механизм итогов позволит получать срез актуальных данных мгновенно, а старые движения можно будет обрезать стандартными средствами свертки базы.

Выясним, какой метод подходит вам больше всего. Если нужно «здесь и сейчас» очистить базу — исправляйте вложенные циклы и запускайте обработку в 5-10 потоков. Если база продолжит расти — планируйте рефакторинг с переходом на ссылочный тип хранения марок и хеширование.

← На главную