Как зафиксировать статус начала проведения документа до завершения транзакции в 1С 8.3

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

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

Типичная попытка решить эту задачу «в лоб» — сделать быструю запись в регистр сведений в самом начале процедуры ОбработкаПроведения, установив флаг ВыполняетсяРасчет = Истина, а в конце сбросить его в Ложь. Однако на практике разработчики обнаруживают, что другие пользователи базы данных не видят изменения статуса вплоть до полного завершения проведения документа. Давайте подробно разберем, почему так происходит, и рассмотрим эффективные варианты решения этой проблемы на платформе 1С:Предприятие 8.3.

Почему стандартная запись в регистр внутри транзакции не работает?

Для того чтобы понять природу этой проблемы, проанализируем поведение транзакций в СУБД и платформе 1С. Любая модификация данных при проведении документа (включая обработчики событий ПередЗаписью, ПриЗаписи и непосредственно ОбработкаПроведения) выполняется в рамках одной общей, неделимой транзакции базы данных.

Современные версии платформы 1С 8.3, работающие на СУБД MS SQL Server, PostgreSQL, IBM DB2 или Oracle, по умолчанию используют уровень изоляции транзакций Read Committed (а для MS SQL Server — его разновидность Read Committed Snapshot Isolation, или RCSI). Основные правила этого режима:

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

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

Ниже мы подробно разберем четыре практических способа решения этой задачи, соответствующих современным стандартам разработки 1С.

Решение №1. Использование фонового задания для записи статуса

Фоновые задания в 1С выполняются в отдельном сеансе на сервере приложений. У них собственное сетевое подключение к СУБД и своя, абсолютно независимая от документа транзакция. Если мы запустим фоновое задание в самом начале проведения документа, оно мгновенно выполнит запись статуса в регистр сведений и зафиксирует изменения в СУБД. Эти данные сразу же станут доступны другим пользователям.

Разберем по шагам реализацию данного механизма. Сначала создадим независимый регистр сведений СтатусыРасчетов с измерением Организация (тип СправочникСсылка.Организации) и ресурсом ВыполняетсяРасчет (тип Булево).

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


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

Теперь в модуле документа (например, в обработчике события ПередЗаписью или в самом начале ОбработкаПроведения) мы инициируем запуск этого фонового задания:


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    
    Если РежимЗаписи = РежимЗаписиДокумента.Проведение Тогда
        
        // Подготовим параметры для фонового задания
        ПараметрыСтатуса = Новый Структура;
        ПараметрыСтатуса.Вставить("Организация", ЭтотОбъект.Организация);
        ПараметрыСтатуса.Вставить("Статус", Истина);
        
        ПараметрыЗапуска = Новый Массив;
        ПараметрыЗапуска.Добавить(ПараметрыСтатуса);
        
        // Запускаем фоновое задание, которое выполнится вне текущей транзакции
        ФоновыеЗадания.Выполнить("ИмяОбщегоМодуля.УстановитьСтатусРасчетаФоновымЗаданием", ПараметрыЗапуска);
        
    КонецЕсли;
    
КонецПроцедуры

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

Решение №2. Использование управляемых блокировок как «Семафора»

Если конечная цель нашей задачи заключается в том, чтобы во время расчета себестоимости заблокировать формирование тяжелых отчетов или предупредить пользователей, нам вообще не обязательно физически записывать данные в таблицы СУБД. Для этого отлично подходит механизм управляемых транзакционных блокировок 1С.

Мы можем наложить исключительную управляемую блокировку на виртуальное пространство (например, на определенную организацию в служебном регистре сведений), которая будет автоматически удерживаться менеджером блокировок 1С до самого окончания транзакции проведения.

Рассмотрим пример установки такой блокировки в обработчике проведения нашего документа:


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    // Инициализируем блокировку данных
    Блокировка = Новый БлокировкаДанных;
    ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.СтатусыРасчетов");
    ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
    ЭлементБлокировки.УстановитьЗначение("Организация", ЭтотОбъект.Организация);
    
    // Блокировка накладывается мгновенно и держится до конца транзакции (все 10 минут)
    Блокировка.Заблокировать();
    
    // Далее следует длительный алгоритм расчетов...
    ВыполнитьДлительныйРасчетСебестоимости(ЭтотОбъект.Организация);
    
КонецПроцедуры

Теперь проанализируем, как использовать эту блокировку в отчете. Перед тем как начать формировать отчет, мы попытаемся установить блокировку на эту же область данных с минимальным временем ожидания (таймаутом). Если блокировка не установилась и система выдала ошибку — значит, в данный момент идет расчет!


Функция ПроверитьВыполнениеРасчета(Организация)
    
    Блокировка = Новый БлокировкаДанных;
    ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.СтатусыРасчетов");
    
    // Запрашиваем разделяемый режим блокировки
    ЭлементБлокировки.Режим = РежимБлокировкиДанных.Разделяемый;
    ЭлементБлокировки.УстановитьЗначение("Организация", Организация);
    
    Попытка
        // Пытаемся заблокировать. Время ожидания ограничено платформой
        Блокировка.Заблокировать();
        // Если блокировка установилась успешно, значит, документ расчета сейчас не проводится
        Возврат Ложь;
    Исключение
        // Исключение сработает, если транзакция проведения удерживает исключительную блокировку
        Возврат Истина;
    КонецПопытки;
    
КонецФункции

Этот метод является самым надежным с точки зрения отказоустойчивости системы. Если сервер аварийно отключится, процесс проведения завершится сбоем или пользователь принудительно закроет программу — управляемая блокировка мгновенно исчезнет сама. Вам не придется вручную чистить «зависшие» в базе данных статусы Истина.

Решение №3. Вынос операции записи за рамки транзакции через переопределение формы

Если запуск проведения всегда осуществляется пользователем интерактивно из экранной формы, мы можем перехватить этот процесс и выполнить запись статуса до того, как транзакция записи документа будет открыта СУБД. Для этого переопределим стандартную команду проведения на форме документа.

Создадим на форме документа собственную команду проведения «Провести расчет» и пропишем в ней следующий алгоритм совместной работы клиента и сервера:


&НаКлиенте
Процедура ПровестиРасчет(Команда)
    
    // Шаг 1. Записываем статус "Выполняется" на сервере вне транзакции документа
    УстановитьСтатусБлокировкиНаСервере(Объект.Организация, Истина);
    
    // Шаг 2. Запускаем стандартную процедуру проведения документа
    ПараметрыЗаписи = Новый Структура;
    ПараметрыЗаписи.Вставить("РежимЗаписи", РежимЗаписиДокумента.Проведение);
    
    Попытка
        Записать(ПараметрыЗаписи);
    Исключение
        // Если проведение завершилось ошибкой, сбрасываем статус блокировки обратно
        УстановитьСтатусБлокировкиНаСервере(Объект.Организация, Ложь);
        ВызватьИсключение;
    КонецПопытки;
    
    // Шаг 3. В случае успешного проведения сбрасываем статус блокировки
    УстановитьСтатусБлокировкиНаСервере(Объект.Организация, Ложь);
    
КонецПроцедуры

&НаСервереБезКонтекста
Процедура УстановитьСтатусБлокировкиНаСервере(Организация, Статус)
    
    МенеджерЗаписи = РегистрыСведений.СтатусыРасчетов.СоздатьМенеджерЗаписи();
    МенеджерЗаписи.Организация = Организация;
    МенеджерЗаписи.ВыполняетсяРасчет = Статус;
    МенеджерЗаписи.Записать(Истина);
    
КонецПроцедуры

Преимуществом данного метода является простота реализации и отсутствие необходимости работать с фоновыми заданиями. Однако помните о существенном ограничении: этот способ не защитит вас, если документ проводится программно (например, групповой обработкой или регламентным заданием), минуя интерактивную форму.

Решение №4. Выход во внешний мир: Использование HTTP-сервисов или внешних файлов

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

Рассмотрим два варианта такой реализации:

  1. Запись флагового файла на диске сервера. В самом начале проведения документа мы создаем на сервере простой текстовый файл с уникальным именем (например, по названию организации). Среда СУБД никак не контролирует файловую систему ОС, поэтому файл появится на диске мгновенно. Смежные процессы и отчеты перед стартом могут проверять существование этого файла с помощью функции НайтиФайлы().
  2. Вызов внутреннего HTTP-сервиса. Из процедуры проведения мы можем инициировать быстрое HTTP-соединение к нашей же опубликованной базе 1С (к специально созданному HTTP-сервису). HTTP-запрос обрабатывается веб-сервером в контексте совершенно другого, независимого сеанса и мгновенно изменяет данные в регистре сведений.

Проанализируем пример кода с отправкой HTTP-запроса из обработчика проведения:


Процедура ОтправитьСтатусЧерезHTTP(ОрганизацияКод, Статус)
    
    // Настраиваем соединение со своей же базой, опубликованной на веб-сервере
    Соединение = Новый HTTPСоединение("localhost", , "Администратор", "Пароль");
    
    // Формируем запрос к нашему HTTP-сервису
    Запрос = Новый HTTPЗапрос("/StatusApp/hs/CalculationStatus/Set");
    Запрос.УстановитьТелоИзСтроки(Строка(Статус) + "," + ОрганизацияКод);
    
    Попытка
        // Отправка запроса происходит мгновенно вне текущей БД-транзакции
        Ответ = Соединение.ОтправитьДляОбработки(Запрос);
    Исключение
        // Обработка ошибок связи
    КонецПопытки;
    
КонецПроцедуры

Решение №5. Кардинальное архитектурное решение: Асинхронные длительные операции

Рассмотренные выше обходные пути помогают решить локальную задачу индикации процесса. Однако с точки зрения архитектурных стандартов разработки фирмы «1С», удержание открытой транзакции СУБД в течение 10 минут — это критическая ошибка (серьезный антипаттерн).

Длительные транзакции неизбежно приводят к блокировкам таблиц, невозможности параллельной работы других пользователей, риску возникновения взаимоблокировок (deadlocks) и взрывному росту файлов логов СУБД (LDF-файлов). В современных системах, таких как 1С:ERP Управление предприятием или 1С:Комплексная автоматизация, длительные расчеты проектируются принципиально иначе.

Рассмотрим правильную схему асинхронного расчета:

  1. При нажатии на кнопку «Провести» документ проводится мгновенно (за 1-2 секунды). Он не делает сложные математические расчеты себестоимости сразу, а лишь меняет свой статус на «Требуется расчет» и записывает данные в очередь заданий.
  2. После этого система автоматически (или интерактивно) запускает асинхронное фоновое задание через стандартные механизмы БСП (Библиотеки Стандартных Подсистем) — модуль ДлительныеОперации.
  3. Фоновое задание производит все сложные вычисления порциями. Каждая порция расчетов записывается в базу данных отдельными, очень быстрыми транзакциями.
  4. Статус выполнения такого расчета всегда легко получить штатными средствами БСП по идентификатору запущенного фонового задания. База данных при этом остается полностью свободной для работы остальных пользователей.

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

← На главную