Почему 1С зависает при массовой обработке документов и как бороться с утечкой памяти SQL?

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

При выполнении массовых операций в 1С, таких как обновление тысяч заказов из внешних XML-файлов, разработчики часто сталкиваются с ситуацией, когда система «замирает», а потребление памяти процессом sqlservr.exe или rphost.exe достигает критических отметок. Рассмотрим подробнее, почему это происходит, и проанализируем конкретный пример кода, который приводит к подобным сбоям.

Разбор ситуации: основные причины «зависания»

В представленном сценарии программист пытается обновить около 1200 заказов. Каждый заказ представлен отдельным файлом XML. При обработке небольших пачек (за один день) система работает стабильно, но при попытке обработать данные за месяц происходит отказ. Выясним причину, изучив представленный алгоритм работы. Проблема здесь комплексная и кроется в нескольких архитектурных узких местах.

1. Утечка памяти во временном хранилище

Посмотрим на фрагмент кода, отвечающий за передачу данных на сервер:


Для каждого ФайлПуть Из МассивФайлов Цикл
    ДвоичныеДанныеЗаказХХХ = Новый ДвоичныеДанные(ФайлПуть.ПолноеИмя);
    УникальныйИдентификаторХранилища = Новый УникальныйИдентификатор;
    Адрес = ПоместитьВоВременноеХранилище(ДвоичныеДанныеЗаказХХХ, УникальныйИдентификатор);
    Результат = ЗагрузитьНаСервере(Адрес);
КонецЦикла;

Проанализируем этот участок. Использование метода ПоместитьВоВременноеХранилище с передачей нового УникальныйИдентификатор внутри цикла — это классическая ошибка, ведущая к прогрессивному росту потребления RAM. В 1С данные, помещенные в хранилище с привязкой к идентификатору формы, живут до тех пор, пока форма не будет закрыта. Поскольку итераций 1200, в памяти сервера приложений (процесс rphost) скапливается 1200 копий бинарных данных файлов.

Как исправить: Необходимо использовать один и тот же адрес для каждой итерации или явно удалять данные. Рекомендуется передавать во второй параметр метода ПоместитьВоВременноеХранилище адрес, полученный в предыдущей итерации, либо не использовать привязку к идентификатору вовсе, если данные нужны только на один вызов.

2. Неэффективный алгоритм поиска (Сложность O(n*m))

Рассмотрим функцию поиска заказа в системе, которую реализовал автор:


Для каждого ЗаказСсылка Из ЗаказыКлиентов Цикл
    Если ЗаказСсылка.ЗаказКлиента.НомерЗаказаХХХ = СтруктураЗаказа.НомерЗаказаХХХ Тогда
        НовыйЗаказКлиента = ЗаказСсылка.ЗаказКлиента.ПолучитьОбъект;
        Прервать;
    КонецЕсли;
КонецЦикла;

Здесь мы видим перебор коллекции ЗаказыКлиентов (таблица значений или массив на форме) внутри цикла по файлам. Если у нас 1200 файлов и в таблице 1200 ссылок, то в худшем случае система выполнит 1 440 000 сравнений. Это создает колоссальную нагрузку на процессор сервера 1С.

Решение: Перед началом цикла обработки файлов следует выгрузить все необходимые заказы в объект Соответствие (Map). Ключом будет выступать НомерЗаказаХХХ, а значением — Ссылка. Поиск в соответствии выполняется методом хэш-таблицы практически мгновенно, что сократит время выполнения этой части кода до минимума.

3. Переполнение памяти SQL Server и лимиты Express-версии

Автор упоминает ограничение в 8 Гб. Это критический порог. При проведении документов 1С выполняет запись во множество таблиц (регистры накопления, сведения, журналы). Каждая транзакция требует места в памяти для кэширования индексов и записи лога транзакций.

Когда память SQL доходит до предела (особенно в MS SQL Server Express), сервер начинает активно использовать файл подкачки на диске. Скорость работы падает в десятки раз, что пользователь воспринимает как «зависание». Перезапуск службы 1С, который помог автору, принудительно разрывает соединения и очищает кэш объектов на стороне сервера приложений, но не решает проблему фрагментации и объемов данных на стороне SQL.

4. Оптимизация работы с объектами и XML

В коде используется чтение через ПотокВПамяти. Хотя это современный подход, в данном случае он избыточен и потребляет лишние ресурсы:


НовыйПоток = Новый ПотокВПамяти;
ЗаписьДанных = Новый ЗаписьДанных(НовыйПоток);
ЗаписьДанных.Записать(ДвоичныеДанныеЗаказХХХ);
ЗаписьДанных.Закрыть;

Разберем, как сделать лучше. Вместо того чтобы гонять байты через память, правильнее использовать метод ЧтениеXML.ОткрытьФайл() непосредственно на сервере, если файлы доступны серверу, либо передавать строку XML. Также крайне важно после завершения работы с объектом НовыйЗаказКлиента убедиться, что ссылка на него не удерживается в памяти. Хотя 1С имеет сборщик мусора, при массовой обработке в одном серверном вызове кэш объектов может раздуваться до огромных размеров.

Итоговые рекомендации по исправлению кода

Проанализировав ситуацию, составим план оптимизации процесса:

  1. Используйте порции данных: Не пытайтесь обработать 1000 документов в одном серверном вызове. Разделите массив файлов на пачки по 50-100 штук и делайте вызов сервера для каждой пачки. Это позволит системе «дышать» и очищать память между вызовами.
  2. Оптимизируйте поиск: Вместо перебора таблицы используйте Соответствие. Подготовьте его один раз перед началом загрузки.
  3. Управляйте временным хранилищем: Используйте УдалитьИзВременногоХранилища(Адрес) в конце каждой итерации цикла на клиенте.
  4. Минимизируйте транзакции: Если это возможно, записывайте документы без проведения (если логика позволяет), а затем проводите их отдельным регламентным заданием или пачками.
  5. Отключайте обработчики: Если при записи документа срабатывают тяжелые подписки на события (например, отправка уведомлений или сложный пересчет цен), на время массовой загрузки их стоит блокировать, используя параметры сеанса или дополнительные реквизиты.

Выясним, как мог бы выглядеть оптимизированный фрагмент поиска документа:


// Предварительная подготовка на сервере
КартаЗаказов = Новый Соответствие;
Для каждого Стр Из ТЧЗаказы Цикл
    КартаЗаказов.Вставить(Стр.НомерЗаказаХХХ, Стр.ЗаказСсылка);
КонецЦикла;

// В цикле обработки файлов
СсылкаНаЗаказ = КартаЗаказов.Получить(НомерИзXML);
Если СсылкаНаЗаказ <> Неопределено Тогда
    ОбъектЗаказа = СсылкаНаЗаказ.ПолучитьОбъект();
    // ... логика обновления ...
КонецЕсли;

Такой подход не только решит проблему с зависанием, но и ускорит работу обработки в несколько раз. Помните, что стабильность 1С при работе с большими данными напрямую зависит от того, насколько бережно вы относитесь к памяти сервера и ресурсам SQL-сервера.

← На главную