Рано или поздно перед разработчиком 1С встает задача экстремально быстрой загрузки данных. Представим ситуацию: нам необходимо разово или периодически (например, раз в полгода для маркетингового анализа) записывать в базу порядка 10 миллионов записей. Это могут быть результаты сложных расчетов, матрицы пересечений товаров и контрагентов или накопленная аналитика из внешних систем. Стандартный построчный перебор и запись каждой строки через менеджер записи в таком случае превратит процесс в многочасовое, а то и многодневное ожидание.
В этой статье мы подробно разберем комплексный подход к решению этой задачи, объединяющий возможности платформы 1С, оптимизацию работы с СУБД и архитектурные хитрости.
Разберем основные причины, по которым запись миллионов строк в 1С «тормозит». Во-первых, это индексы. При каждой записи СУБД должна обновлять индексы таблицы, что требует значительных ресурсов дисковой подсистемы и процессора. Во-вторых, механизм итогов. Если мы пишем в регистр сведений или накопления, система пересчитывает итоги для каждой транзакции. В-третьих, блокировки данных. При массовой записи в одну таблицу возникают конфликты блокировок, особенно если включены управляемые блокировки. И наконец, накладные расходы объектной модели: создание программного объекта для каждой записи в цикле — крайне неэффективная операция.
Первое, что мы должны сделать — это задействовать всю мощь серверного оборудования. Вместо одного потока мы разделим наши 10 миллионов записей на части и запустим несколько фоновых заданий. Рассмотрим, как правильно рассчитать количество потоков. Оптимально использовать формулу: КоличествоЛогическихПроцессоров - 1, либо даже в 1.5–2 раза больше, если дисковая подсистема позволяет эффективно обрабатывать очереди записи.
Рассмотрим пример кода для запуска параллельной обработки:
// Определяем количество порций данных
РазмерПорции = 100000; // 100 тысяч записей на одно задание
ВсегоЗаписей = ТЗДанных.Количество();
ТекущаяПозиция = 0;
МассивЗаданий = Новый Массив;
Пока ТекущаяПозиция < ВсегоЗаписей Цикл
КонечнаяПозиция = Мин(ТекущаяПозиция + РазмерПорции, ВсегоЗаписей);
ПорцияДанных = ВырезатьЧастьТаблицы(ТЗДанных, ТекущаяПозиция, КонечнаяПозиция);
ПараметрыЗадания = Новый Массив;
ПараметрыЗадания.Добавить(ПорцияДанных);
Задание = ФоновыеЗадания.Выполнить("ОбщийМодульСервер.ЗаписатьПорциюВРегистр", ПараметрыЗадания);
МассивЗаданий.Добавить(Задание);
ТекущаяПозиция = КонечнаяПозиция;
КонецЦикла;
ФоновыеЗадания.ОжидатьЗавершения(МассивЗаданий);
Внутри фонового задания важно использовать транзакции оптимального размера. Опытным путем установлено, что транзакции по 10 000 – 50 000 записей показывают лучшую производительность. Слишком большие транзакции перегружают tempdb и лог транзакций SQL-сервера.
Проанализируем методы ускорения записи внутри самого фонового задания. Простейший и самый важный шаг — использование свойства ОбменДанными.Загрузка = Истина. Это отключает выполнение кода в подписках на события и стандартные проверки, что существенно экономит время процессора.
Посмотрим на пример эффективной записи набора данных:
Процедура ЗаписатьПорциюВРегистр(ТаблицаДанных) Экспорт
Набор = РегистрыСведений.МаркетинговыеДанные.СоздатьНаборЗаписей();
// Важно: если мы только добавляем данные, не используем отбор
// Если нужно замещать порциями - устанавливаем отбор по ключевому полю
Набор.ОбменДанными.Загрузка = Истина;
Набор.Загрузить(ТаблицаДанных);
Попытка
Набор.Записать();
Исключение
ЗаписатьЛогОшибки(ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
Внимание: Если ваш регистр имеет много измерений, запись будет идти медленнее из-за сложности индексов. Если бизнес-логика позволяет, перед началом загрузки стоит отключить итоги у регистра программно через УстановитьИспользованиеИтогов(), а после завершения всех заданий — включить их обратно. Это позволит СУБД пересчитать итоги один раз для всего объема данных, а не делать это миллионы раз.
Выясним, что можно сделать на стороне SQL-сервера, чтобы ускорить процесс. Если база работает в режиме Full Recovery Model, то при записи 10 миллионов строк файл лога транзакций (.ldf) разрастется до гигантских размеров, что может привести к остановке сервера.
Проанализируем алгоритм действий системного администратора перед загрузкой:
TempDB расположена на быстром диске (SSD/NVMe). Массовая запись активно использует временные таблицы для сортировки данных перед фиксацией.Shrink лога, если он все же разросся.Если быстродействия штатными средствами все еще недостаточно, разберем вариант с использованием прямых SQL-запросов. Это метод «вне рамок лицензионного соглашения» для критических случаев, но он обеспечивает максимальную скорость. Самый быстрый способ — это выгрузка данных из 1С во внешний CSV-файл и последующее выполнение команды BULK INSERT на стороне SQL-сервера.
Посмотрим на схему этого процесса:
bcp.INSERT INTO ... SELECT.Такой подход позволяет записать 10 миллионов строк буквально за пару минут, так как SQL Server выполняет операцию на уровне страниц данных, минуя интерпретатор 1С.
Рассмотрим ситуацию, когда 10 миллионов записей нужны только для построения отчетов. Нужно ли вообще «раздувать» основную базу 1С? Современный стек технологий предлагает использовать ClickHouse — колоночную базу данных, специально созданную для аналитики больших объемов данных.
В этом случае наши действия будут следующими:
Выясним краткий чек-лист для успешной записи сверхбольших объемов:
ОбменДанными.Загрузка = Истина.Применяя эти методы в комплексе, мы сможем сократить время записи 10 миллионов строк с нескольких часов до считанных минут, обеспечив стабильную работу системы и комфорт пользователей.