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

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

При разработке механизмов автоматизации в «1С:Предприятие 8» (особенно в крупных и зрелых конфигурациях, таких как УПП 1.3) программисты часто сталкиваются с двумя классическими проблемами: неэффективным написанием кода, приводящим к критическому снижению производительности системы, и трудностями при первом запуске и отладке регламентных (фоновых) заданий. Разберем детально обе проблемы на практическом примере задачи по автоматическому отключению просроченных договоров контрагентов.

Анализ проблемы: почему выборка всей таблицы — это критическая ошибка

Рассмотрим исходный вариант кода, который изначально предложил разработчик на форуме:


Процедура ГЭС_ДоговораКонтрагентовНеДействующие() Экспорт
    Выборка = Справочники.ДоговорыКонтрагентов.Выбрать();
    Пока Выборка.Следующий() Цикл
        Если Выборка.ГЭС_СтатусДоговора = Перечисления.ГЭС_СтатусыДоговоров.Действует И Выборка.СрокДействия < ТекущаяДата() И НЕ Выборка.СрокДействия = Дата('00010101') И (Выборка.ГЭС_ВидДействияДоговора = Перечисления.ГЭС_ВидДейстивияДоговора.Бессрочный ИЛИ Выборка.ГЭС_ВидДействияДоговора = "") Тогда
            ВыборкаОбъект = Выборка.ПолучитьОбъект();
            ВыборкаОбъект.ГЭС_СтатусДоговора = Перечисления.ГЭС_СтатусыДоговоров.НеДействует;
            ВыборкаОбъект.Записать();
        КонецЕсли;    
    КонецЦикла;    
КонецПроцедуры

Этот код действительно «работает», если запустить его вручную на небольшой тестовой базе. Однако использовать его в реальной рабочей базе категорически запрещено. Метод Справочники.ДоговорыКонтрагентов.Выбрать() без параметров выполняет последовательное чтение всей таблицы справочника из СУБД в оперативную память сервера приложений.

Проанализируем последствия такого подхода:

  1. Огромная нагрузка на оперативную память и сеть: Если в вашей базе накоплены сотни тысяч или миллионы договоров за много лет, сервер 1С будет вынужден прочитать их все. Это вызовет резкий скачок потребления памяти и загрузки сети между сервером 1С и СУБД.
  2. Избыточное создание тяжелых объектов: Метод ПолучитьОбъект() создает полноценный XML-объект в памяти со всеми его табличными частями и реквизитами. Делать это для элементов, которые не требуют изменений — крайне нерационально.
  3. Блокировки таблиц: Длительный перебор справочника в цикле с последующей записью отдельных объектов может вызывать взаимоблокировки (deadlocks) в СУБД, мешая работе обычных пользователей.

Для понимания сути этой ошибки уместно использовать бытовую аналогию, прозвучавшую на форуме. Представьте, что вам нужно сварить гречневую кашу. Вместо того чтобы пойти в магазин и купить ровно один пакет гречки, вы покупаете абсолютно все крупы, которые есть на прилавках супермаркета, приносите их домой и перед кастрюлей начинаете перебирать: «Если это гречка — кладем в кастрюлю, иначе — выбрасываем». Именно так работает метод Выбрать() с последующей фильтрацией в кодовой конструкции Если...Тогда.

Правильное решение: оптимизация кода через механизм запросов

Чтобы избежать лишней нагрузки, мы должны делегировать фильтрацию данных самой СУБД. Программа должна получать из базы данных только ссылки на те договоры, которые гарантированно удовлетворяют нашим критериям отбора. Модифицируем код процедуры, используя объект Запрос:


Процедура ГЭС_ДоговораКонтрагентовНеДействующие() Экспорт
    
    // Инициализируем запрос для предварительного отбора данных на уровне СУБД
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	Договоры.Ссылка КАК Ссылка
        |ИЗ
        |	Справочник.ДоговорыКонтрагентов КАК Договоры
        |ГДЕ
        |	Договоры.ГЭС_СтатусДоговора = ЗНАЧЕНИЕ(Перечисление.ГЭС_СтатусыДоговоров.Действует)
        |	И Договоры.СрокДействия < &ТекущаяДата
        |	И Договоры.СрокДействия <> ДАТАВРЕМЯ(1, 1, 1)
        |	И (Договоры.ГЭС_ВидДействияДоговора = ЗНАЧЕНИЕ(Перечисление.ГЭС_ВидДейстивияДоговора.Бессрочный) 
        |		ИЛИ Договоры.ГЭС_ВидДействияДоговора = ЗНАЧЕНИЕ(Перечисление.ГЭС_ВидДейстивияДоговора.ПустаяСсылка));";
    
    // В качестве параметра передаем начало сегодняшнего дня, чтобы избежать проблем с часовыми поясами и секундами
    Запрос.УстановитьПараметр("ТекущаяДата", НачалоДня(ТекущаяДата()));
    
    Выборка = Запрос.Выполнить().Выбрать();
    
    // Обходим исключительно те элементы, которые реально требуют модификации
    Пока Выборка.Следующий() Цикл
        
        // Получаем объект только для перезаписи
        ДоговорОбъект = Выборка.Ссылка.ПолучитьОбъект();
        ДоговорОбъект.ГЭС_СтатусДоговора = Перечисления.ГЭС_СтатусыДоговоров.НеДействует;
        
        Попытка
            // Включаем режим загрузки для обхода тяжелых проверок при записи, если это допустимо бизнес-логикой
            ДоговорОбъект.ОбменДанными.Загрузка = Истина;
            ДоговорОбъект.Записать();
        Исключение
            // Гарантируем, что ошибка в одном договоре не прервет выполнение всего регламентного задания
            ЗаписьЖурналаРегистрации(
                "РегламентноеЗадание.Договоры",
                УровеньЖурналаРегистрации.Ошибка,
                Метаданные.Справочники.ДоговорыКонтрагентов,
                Выборка.Ссылка,
                "Не удалось обновить статус договора. Описание ошибки: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке())
            );
        КонецПопытки;
        
    КонецЦикла;    
    
КонецПроцедуры

Рассмотрим подробнее преимущества оптимизированного варианта:

Выясняем причины: почему регламентное задание не запускается по расписанию

Если код процедуры полностью отлажен и стабильно работает при ручном запуске через внешнюю обработку, но регламентное задание упорно молчит, необходимо последовательно проверить системные настройки платформы «1С:Предприятие».

Проанализируем основные причины, по которым планировщик заданий игнорирует запуск вашей процедуры:

1. Специфика файлового режима работы базы данных

В файловой версии базы данных (в отличие от клиент-серверной SQL-версии) нет выделенного процесса планировщика. Регламентные задания могут выполняться только в контексте какого-либо запущенного клиентского сеанса 1С. Если все пользователи закрыли программу, ни одно задание не запустится.

Для стабильной работы планировщика в файловой базе необходимо выделить отдельный технический компьютер (или виртуальную машину), запустить на нем сеанс «1С:Предприятия» под специальным пользователем и периодически инициировать обработку заданий с помощью метода ВыполнитьОбработкуЗаданий(). Также можно настроить встроенный обработчик ожидания:

ПодключитьОбработчикОжидания("ВыполнитьОбработкуЗаданий", 60);

2. Блокировка регламентных заданий в клиент-серверном режиме

Если база работает на сервере под управлением MS SQL или PostgreSQL, очень часто выполнение регламентных заданий блокируется администраторами принудительно (например, при создании копии базы, чтобы тестовая база не отправляла реальные письма или не выгружала данные на сайт).

Чтобы проверить этот параметр, выполните следующие шаги:

  1. Откройте утилиту «Администрирование серверов 1С:Предприятия».
  2. Найдите вашу информационную базу в списке.
  3. Откройте свойства базы и убедитесь, что флаг «Блокировка регламентных заданий включена» снят. Если он установлен, снимите его и примените изменения.

3. Настройки свойств Общего модуля

Регламентное задание может вызывать процедуры только из общих модулей с определенными свойствами. Откройте свойства вашего общего модуля в конфигураторе и проверьте флаги:

4. Использование внешней обработки «Консоль заданий»

В типовых конфигурациях (особенно в УПП 1.3 на обычных формах) новые регламентные задания, созданные в конфигураторе, не всегда автоматически регистрируются в базе данных со статусом «Предопределенное». Для управления ими необходимо использовать специализированную обработку «Консоль заданий» (ее можно найти на диске ИТС или в составе дополнительных инструментов разработчика).

С помощью «Консоли заданий» выполните следующие действия:

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

Как настроить отладку фонового задания в конфигураторе

Фоновые задания запускаются сервером обособленно, поэтому обычная точка останова в коде общего модуля по умолчанию не сработает. Чтобы зайти отладчиком в процедуру регламентного задания, выполните следующие шаги по настройке конфигуратора:

  1. Убедитесь, что служба сервера 1С запущена с ключом отладки (параметр -debug в строке запуска службы в реестре Windows или конфигурационном файле под Linux). Если ключ отсутствует, отладка на сервере работать не будет.
  2. В конфигураторе перейдите в меню Отладка -> Подключение...
  3. В открывшемся окне нажмите кнопку «Настройка автоматического подключения».
  4. Установите флажок напротив пункта «Фоновые задания» и нажмите «ОК».
  5. Поставьте точку останова на первой строчке вашей процедуры в общем модуле.
  6. Инициируйте запуск задания вручную через «Консоль заданий» или программно с помощью метода ФоновыеЗадания.Выполнить(). Конфигуратор автоматически перехватит управление, и вы сможете пошагово проанализировать выполнение кода.

Применяя данные рекомендации, мы не только решим проблему с запуском фоновой задачи, но и сделаем работу системы стабильной и быстрой, исключив появление избыточных блокировок и зависаний сервера приложений.

← На главную