В практике разработчика 1С часто возникают задачи, связанные с обработкой огромных массивов данных. Одной из самых трудоемких операций является полное перезаполнение или массовое обновление записей в регистрах сведений, которые имеют режим записи «Подчинение регистратору». Традиционный подход, требующий создания набора записей с обязательным отбором по регистратору для каждого документа, при больших объемах (миллионы строк) работает крайне медленно из-за огромного количества транзакций и обращений к СУБД.
В данной статье мы подробно разберем, как обойти эти ограничения, рассмотрим революционные изменения в платформе 8.3.26, изучим возможности прямой работы с SQL и методы параллельной обработки данных.
Прежде чем перейти к решениям, проанализируем ситуацию. В обычных условиях для изменения записей в подчиненном регистре нам необходимо выполнить следующий алгоритм:
РегистрСведенийНаборЗаписей.Регистратору.Если у нас 100 000 регистраторов, система выполнит 100 000 отдельных транзакций записи. Каждая транзакция сопровождается блокировками, обновлением индексов и проверками ссылочной целостности. Это создает колоссальную нагрузку на сервер и дисковую подсистему. Давайте выясним, как это ускорить.
Это самый современный и предпочтительный метод. Начиная с версии платформы 8.3.26, разработчики 1С радикально изменили подход к записи данных в регистры. Теперь менеджер регистра сведений получил возможность записывать таблицу значений целиком, игнорируя обязательную привязку к конкретному регистратору в рамках одного вызова.
Рассмотрим подробнее, как это работает. Раньше метод Записать() у менеджера регистра использовался в основном для независимых регистров. Теперь для подчиненных регистров мы можем подготовить одну большую таблицу значений со всеми данными (включая колонку Регистратор и НомерСтроки) и передать её платформе.
Рассмотрим пример кода:
// Предположим, у нас есть ТаблицаНовыхДанных с колонками:
// Период, Регистратор, НомерСтроки, Измерение1, Ресурс1...
Менеджер = РегистрыСведений.НазваниеВашегоРегистра;
Менеджер.Записать(ТаблицаНовыхДанных);
Почему это быстрее? Платформа сама оптимизирует процесс записи на уровне СУБД, стараясь минимизировать количество команд INSERT и максимально эффективно использовать пакетную запись. При этом важно помнить про режим ОбменДанными.Загрузка = Истина, который отключает лишние проверки и обработчики событий, что дает дополнительный прирост скорости.
Важное условие: в передаваемой таблице значений данные должны быть уникальны по комбинации измерений, регистратора и номера строки. В противном случае платформа выдаст ошибку уникальности записей.
Если вы работаете на более ранних версиях платформы или объем данных настолько велик, что штатные средства не справляются, можно рассмотреть вариант прямого взаимодействия с таблицами SQL. Посмотрим на этот процесс с точки зрения администратора БД.
Выясним причину замедления на уровне SQL — это обновление индексов. При массовой вставке строк SQL-сервер вынужден перестраивать индексы (деревья) для каждой новой порции данных. Для ускорения процесса опытные разработчики используют следующую стратегию:
DISABLED.DELETE используется команда TRUNCATE TABLE (если нужно очистить весь регистр) или DELETE порциями без ведения детального лога транзакций.INSERT INTO ... SELECT из временной таблицы).Внимание! Этот метод является «грязным» с точки зрения архитектуры 1С. Платформа может не «узнать» об изменениях в базе (кэш объектов на сервере приложений останется старым). После таких манипуляций необходимо как минимум перезапустить сеансы или очистить кэш, а также убедиться в целостности итогов, если регистр связан с какими-либо расчетами.
Проанализируем ситуацию, когда нам нужно остаться в рамках штатных механизмов 1С, но при этом максимально задействовать ресурсы сервера. Разделим задачу на части.
Основная идея заключается в том, чтобы разбить весь объем регистраторов на «пакеты» и обрабатывать каждый пакет в отдельном потоке. Разберем по шагам:
Регистраторов, которые требуют перезаписи.Пример логики распределения:
// Код в основной обработке
МассивРегистраторов = ПолучитьСписокРегистраторовДляОбновления();
КоличествоПотоков = 8;
РазмерПорции = Цел(МассивРегистраторов.Количество() / КоличествоПотоков);
Для НомерПотока = 1 По КоличествоПотоков Цикл
ПараметрыЗадания = Новый Структура("ПорцияРегистраторов", ПолучитьЧастьМассива(МассивРегистраторов, НомерПотока, РазмерПорции));
ФоновыеЗадания.Выполнить("ВашМодуль.ВыполнитьПерезаписьПорции", ПараметрыЗадания);
КонецЦикла;
Важный нюанс: при параллельной записи необходимо избегать взаимных блокировок (deadlocks). Убедитесь, что разные потоки не пытаются одновременно записывать данные, относящиеся к одним и тем же диапазонам индексов или одним и тем же регистраторам.
Иногда быстрее не обновлять существующий регистр, а «подменить» его содержимое. Рассмотрим сценарий использования временных объектов.
Если задача позволяет, можно создать независимый вспомогательный регистр с аналогичной структурой. Загрузка в независимый регистр всегда происходит быстрее, так как платформа позволяет использовать метод Загрузить() для набора записей без привязки к регистратору (даже в старых версиях). После того как данные подготовлены в промежуточном регистре, мы можем перенести их в основной регистр порциями или через SQL-запрос на уровне сервера, что будет значительно быстрее, чем построчная обработка в коде 1С.
Также не забывайте про оптимизацию состава измерений. Каждое лишнее измерение — это дополнительный индекс. Если какие-то данные можно вынести в Ресурсы или Реквизиты, это существенно повысит скорость записи, так как индексы по ним не строятся.
Подведем итог нашему анализу:
Менеджер.Записать(ТаблицаЗначений). Это самый безопасный и быстрый способ.ОбменДанными.Загрузка = Истина перед записью набора, чтобы минимизировать накладные расходы платформы.Выбор конкретного метода зависит от версии вашей системы, доступности SQL-консоли и критичности времени простоя базы. Сочетание этих подходов позволит вам сократить время перезаписи регистра с часов до считанных минут.