Многие разработчики сталкиваются с парадоксальной ситуацией: одна и та же операция записи в регистры сведений выполняется в файловой базе за считанные минуты, а на мощном SQL-сервере растягивается на часы. На первый взгляд это кажется нелогичным, ведь серверная СУБД создана для высоких нагрузок. Однако при работе в один поток файловая база часто выигрывает за счет отсутствия сетевых задержек и упрощенного механизма транзакций. В этой статье мы разберем, как сократить этот разрыв и заставить SQL-сервер работать на полную мощность.
Для начала проанализируем ситуацию. В файловом варианте 1С напрямую пишет в файл базы данных. В серверном же варианте между кодом и физическим диском стоят дополнительные звенья: сервер приложений 1С и SQL Server. Основная задержка при выполнении метода НаборЗаписей.Записать() в цикле обычно связана с журналом транзакций (LDF). SQL Server обязан гарантировать сохранность данных, поэтому после каждой записи он дожидается подтверждения от дисковой подсистемы о том, что транзакция зафиксирована в логе. Если мы пишем данные мелкими порциями в цикле, количество таких физических обращений к диску становится критическим.
Если мы будем записывать каждый набор записей отдельно, 1С и SQL будут создавать «неявную» транзакцию на каждую операцию. Это создает огромную нагрузку на дисковую подсистему. Рассмотрим, как можно ускорить процесс, объединив записи в одну логическую операцию.
Выясним причину: при использовании метода НачатьТранзакцию() мы сообщаем SQL Server, что все последующие изменения нужно накопить и зафиксировать «одним махом». Рассмотрим пример кода:
НачатьТранзакцию();
Попытка
Для Каждого НаборДанных Из МассивНаборов Цикл
Набор = РегистрыСведений.МойРегистр.СоздатьНаборЗаписей();
Набор.Отбор.Ключ.Установить(НаборДанных.Ключ);
Набор.Загрузить(НаборДанных.ТаблицаЗаписей);
Набор.Записать();
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписатьВЛогОшибку(ОписаниеОшибки());
КонецПопытки;
Важный момент: не стоит делать транзакцию бесконечно большой. Если данных миллионы, это может привести к переполнению журнала транзакций или блокировке всей таблицы. Оптимально записывать данные порциями (например, по 1000 или 5000 наборов записей в одной транзакции).
Как показала практика обсуждения, это один из самых эффективных способов. Если один поток записи в SQL работает медленнее, чем в файловой базе, мы можем запустить 10 потоков одновременно. SQL Server прекрасно справляется с параллелизмом, в отличие от файловой базы, которая в этот момент просто «встанет» из-за блокировок файла.
Разберем по шагам, как реализовать многопоточную запись:
Пример инициализации фоновых заданий:
// Разделяем массив ключей на части
РазмерПорции = 1000;
Для НомерПорции = 1 По КоличествоПорций Цикл
ПараметрыЗапуска = Новый Массив;
ПараметрыЗапуска.Добавить(ДанныеПорции);
ФоновыеЗадания.Выполнить("МойОбщийModule.ЗаписатьПорциюДанных", ПараметрыЗапуска);
КонецЦикла;
Проанализируем результат: при разделении на 4-8 потоков (в зависимости от количества ядер процессора) общая скорость записи в серверной базе может вырасти в разы, значительно опередив файловый вариант.
Посмотрим на настройки самой СУБД. Зачастую SQL «из коробки» не оптимизирован под специфику 1С. Рассмотрим ключевые параметры:
Simple. Это значительно сократит объем записываемой информации в журнал транзакций.1 (или в некоторых случаях 2 или 4, но не оставлять 0). Это предотвращает ситуацию, когда один сложный запрос «отъедает» все ресурсы процессора, мешая другим операциям.UPDATE STATISTICS после или во время загрузки крупных порций.В серверном варианте 1С тратит ресурсы на управление блокировками. Проанализируем ситуацию: если мы выполняем регламентную загрузку данных и в базе никого нет, можно использовать Монопольный режим. В этом случае сервер 1С оптимизирует работу с памятью и блокировками, что дает дополнительный прирост производительности.
Также убедитесь, что у регистра сведений установлен режим Управляемый для блокировок. Это позволит SQL Server более гибко управлять доступом к данным, не блокируя всю таблицу целиком при записи одного набора, что критично для многопоточной работы, описанной в Решении 2.
Подведем итог. Чтобы SQL-база не уступала файловой в скорости записи, необходимо:
Записать() в блоки.Помните, что серверная база выигрывает не на скорости одной конкретной операции, а на способности выполнять сотни таких операций одновременно без потери стабильности и целостности данных.