Работа с большими массивами данных в 1С:Предприятие 8.3 всегда требует особого подхода к архитектуре решения. Ситуация, когда нужно обновить ресурс в регистре сведений, содержащем миллионы записей, часто приводит к аварийному завершению фоновых заданий, переполнению памяти сервера или блокировкам базы данных. Разберем по шагам, почему стандартные подходы могут не работать и как правильно организовать процесс массового заполнения данных.
Рассмотрим типичную ситуацию: разработчик пытается запустить 20 фоновых заданий, каждое из которых обрабатывает свою порцию данных. Однако через некоторое время все задания завершаются с ошибкой. Проанализируем, почему это происходит:
НачатьТранзакцию() и ЗафиксироватьТранзакцию() внутри цикла для каждой записи создает колоссальную нагрузку на лог транзакций (LDF-файл) и замедляет работу за счет постоянных дисковых операций записи.Вопреки расхожему мнению, многопоточность при записи в одну таблицу не всегда дает прирост скорости. Часто один стабильный поток, работающий в технологическое окно (ночью), оказывается эффективнее и надежнее. Если же необходимо использовать параллельность, разберем, как правильно подготовить данные.
Проанализируем типичную ошибку распределения данных: нельзя просто выбирать записи порциями по N штук в каждом потоке, если условия выбора могут пересекаться. Правильнее заранее подготовить список ключей записей или четко разбить диапазон (например, по дате или по хэшу уникального идентификатора), чтобы потоки гарантированно не обрабатывали одни и те же данные.
Если специфика регистра требует использования объекта РегистрСведенийМенеджерЗаписи (например, когда измерения разные для каждой строки), необходимо придерживаться следующих правил. Выясним, как ускорить этот процесс:
Во-первых, обязательно используйте свойство ОбменДанными.Загрузка = Истина. Это отключает проверки в модулях набора записей, что критично при массовых операциях. Рассмотрим пример правильной организации цикла:
// Пример обработки порции данных
Процедура ОбработатьПорцию(МассивДанных)
Для Каждого СтрокаДанных Из МассивДанных Цикл
Менеджер = РегистрыСведений.ИмяРегистра.СоздатьМенеджерЗаписи();
// Заполняем измерения для поиска
ЗаполнитьЗначенияСвойств(Менеджер, СтрокаДанных);
Менеджер.Прочитать();
Если Менеджер.Выбран() Тогда
// Заполняем новый ресурс
Менеджер.НовыйРесурс = ВычислитьЗначение(СтрокаДанных);
Менеджер.ОбменДанными.Загрузка = Истина;
Попытка
Менеджер.Записать();
Исключение
// Логируем ошибку и продолжаем
КонецПопытки;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Посмотрим на ситуацию с точки зрения производительности СУБД. Запись через НаборЗаписей обычно быстрее, чем через МенеджерЗаписи, так как платформа оптимизирует SQL-запросы. Даже если у вас независимый регистр и вы меняете только одну запись, запись набора из одной строки может работать эффективнее в массовом цикле.
Однако помните: при записи набора записей с установленным отбором, 1С сначала удаляет все записи, соответствующие отбору, а затем вставляет новые. Для независимого регистра без отборов это может быть опасно, поэтому всегда проверяйте установку фильтров.
Разберем важный нюанс: размер порции в одной транзакции. Не стоит пытаться записать все 1.5 млн записей в одной транзакции — это гарантированно приведет к ошибке переполнения лога транзакций. Опытным путем установлено, что оптимальный размер порции составляет от 1 000 до 10 000 записей.
Проанализируем алгоритм с промежуточной фиксацией:
Sleep) или просто переходим к следующей итерации, позволяя системе "вдохнуть" и освободить ресурсы.Часто причиной того, что фоновые задания "молотят вхолостую", является неверный запрос в начале обработки. Выясним причину на примере составных типов. Если вы ищете записи, где ресурс еще не заполнен, и используете обращение через точку (например, БизнесПроцесс.Шаблон), помните:
В языке запросов 1С обращение через точку к пустому значению составного типа или ссылке приведет к значению NULL. Проверка ЕСТЬ NULL — это эффективный способ отсечь уже обработанные данные на уровне СУБД. Это гораздо быстрее, чем выгружать все 1.5 млн записей в память 1С и проверять их в коде.
ВЫБРАТЬ ПЕРВЫЕ 10000
Рег.Измерение1,
Рег.Измерение2
ИЗ
РегистрСведений.ИмяРегистра КАК Рег
ГДЕ
Рег.НовыйРесурс = ЗНАЧЕНИЕ(Справочник.ЗначенияРесурса.ПустаяСсылка)
И НЕ Рег.БизнесПроцесс.Ссылка ЕСТЬ NULL
Чтобы избежать падения всей системы при заполнении 1.5 млн записей, придерживайтесь следующих советов:
Истина при массовой модификации данных, чтобы избежать запуска тяжелых обработчиков событий ПриЗаписи.Таким образом, для успешного решения задачи нам не обязательно использовать экстремальное распараллеливание. Сочетание правильной работы с транзакциями, использования ОбменДанными.Загрузка и грамотно составленных запросов для выбора порций данных позволяет обработать 1.5 млн записей за разумное время (от 30 минут до пары часов) без риска остановки работы пользователей.