Почему 1С зависает при проведении документа с установкой Отказ = Истина?

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

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

Разберем причину: почему происходит зависание?

Когда мы пишем код в процедуре ОбработкаПроведения, всё выполнение происходит внутри одной неявной транзакции. Установка параметра Отказ = Истина не прерывает выполнение кода мгновенно. Это лишь сигнал платформе, что по завершении процедуры транзакцию нужно не зафиксировать (Commit), а откатить (Rollback).

Проанализируем ситуацию: если до момента установки отказа программа успела выполнить тяжелые операции (например, записать тысячи строк в регистры или создать множество временных таблиц), то при выполнении команды ROLLBACK серверу СУБД (SQL Server или PostgreSQL) требуется время и ресурсы, чтобы вернуть данные в исходное состояние. Если в этот момент база зависает, это может означать следующее:

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

Шаг 1. Оптимизация контроля остатков (уходим от запросов в цикле)

Первое, на что стоит обратить внимание в коде автора — это выполнение запроса внутри цикла. Рассмотрим, как делать нельзя:


Для Каждого Позиция Из ТЧНоменклатура Цикл
    // Это убивает производительность! 
    Проверка = ОбщиеФункции.ПроверкаОстаткаНаСкладе(Позиция.Номенклатура, Позиция.Склад); 
    Если Проверка Тогда
        Отказ = Истина;
        Прервать;
    КонецЕсли;
КонецЦикла;

Если в документе 100 строк, система сделает 100 отдельных обращений к серверу БД. Это создает колоссальную нагрузку. Правильный подход — получить все данные одним запросом, используя временные таблицы. Перепишем процедуру ОбработкаПроведения, используя современную методику:


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    // 1. Подготовим движения
    Движения.РегистрХранения.Записывать = Истина;
    Движения.РегистрХранения.Очистить();
    
    // 2. Создадим запрос для получения данных из ТЧ и остатков одновременно
    Запрос = Новый Запрос;
    Запрос.Текст = 
    "ВЫБРАТЬ
    |	СписаниеТовары.Номенклатура КАК Номенклатура,
    |	СписаниеТовары.Склад КАК Склад,
    |	СУММА(СписаниеТовары.Количество) КАК КоличествоДокумент
    |ПОМЕСТИТЬ ВТ_Товары
    |ИЗ
    |	Документ.СписаниеНоменклатуры.Номенклатура КАК СписаниеТовары
    |ГДЕ
    |	СписаниеТовары.Ссылка = &Ссылка
    |ГРУППИРОВАТЬ ПО
    |	СписаниеТовары.Номенклатура,
    |	СписаниеТовары.Склад
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |	ВТ_Товары.Номенклатура КАК Номенклатура,
    |	ВТ_Товары.Склад КАК Склад,
    |	ВТ_Товары.КоличествоДокумент КАК КоличествоДокумент,
    |	ЕСТЬNULL(РегистрХраненияОстатки.КоличествоОстаток, 0) КАК КоличествоОстаток
    |ИЗ
    |	ВТ_Товары КАК ВТ_Товары
    |		ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.РегистрХранения.Остатки(&МоментВремени, 
    |           (Номенклатура, Склад) В (ВЫБРАТЬ Номенклатура, Склад ИЗ ВТ_Товары)) КАК РегистрХраненияОстатки
    |		ПО ВТ_Товары.Номенклатура = РегистрХраненияОстатки.Номенклатура
    |			И ВТ_Товары.Склад = РегистрХраненияОстатки.Склад";

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

КонецПроцедуры

Шаг 2. Выясняем, почему отладчик не помогает

Проанализируем ситуацию, когда точка останова в ОбработкаПроведения не срабатывает. Это часто случается в клиент-серверных версиях. Чтобы «поймать» ошибку, нужно проверить следующее:

  1. Флаг отладки на сервере: Убедитесь, что служба агента 1С запущена с параметром -debug.
  2. Журнал регистрации: Если отладчик не работает, используйте метод ЗаписьЖурналаРегистрации. Разставим «маячки» в коде, чтобы понять, на какой строке происходит зависание.

ЗаписьЖурналаРегистрации("ОтладкаПроведения", УровеньЖурналаРегистрации.Информация, , , "Начало проверки");
// Ваш код
ЗаписьЖурналаРегистрации("ОтладкаПроведения", УровеньЖурналаРегистрации.Информация, , , "Перед установкой Отказ = Истина");
Отказ = Истина;
ЗаписьЖурналаРегистрации("ОтладкаПроведения", УровеньЖурналаРегистрации.Информация, , , "Конец процедуры");

Если в журнале есть запись «Перед установкой Отказ», но нет «Конец процедуры», значит, проблема в системных механизмах отката транзакции или в подписках на события ПриЗаписи.

Шаг 3. Архитектура регистров (Измерения vs Ресурсы)

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

Если мы добавим Штрихкод в измерения, система будет хранить остатки по каждой комбинации «Номенклатура + Склад + Штрихкод». Если штрихкодов много, таблица итогов регистра «распухнет», и любой запрос к ней (особенно при откате транзакции) будет вешать базу.

Рассмотрим рекомендации по проектированию:

Методика проведения «От обратного» (Новая методика)

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

  1. Устанавливаем Движения.РегистрХранения.Записывать = Истина;.
  2. Записываем все движения документа (даже если товара может не хватать).
  3. Вызываем Движения.Записать(); — это принудительно сбросит данные в базу внутри транзакции.
  4. Выполняем запрос к остаткам этого же регистра, фильтруя только те позиции, где КоличествоОстаток < 0.
  5. Если такие позиции есть — выводим сообщение и ставим Отказ = Истина.

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

Итоги: как решить проблему зависания?

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

План действий для исправления:

  1. Перепишите код проведения: уберите запросы из циклов.
  2. Проверьте структуру регистра: уберите лишние измерения, которые не участвуют в расчете баланса.
  3. Если отладчик не работает, используйте ЗаписьЖурналаРегистрации для локализации «зависшего» участка.
  4. Обновите платформу до актуального релиза (проблемы с зависанием транзакций часто исправляются в обновлениях).

Используя эти методы, мы не только избавимся от зависания, но и сделаем систему значительно более отзывчивой и надежной.

← На главную