Ситуация, когда документ корректно проводится при успешной проверке, но «намертво» вешает систему при попытке отмены проведения через Отказ = Истина, — это классическая проблема, с которой сталкиваются многие разработчики. На первый взгляд кажется, что в коде бесконечный цикл, но на самом деле причина часто кроется в особенностях работы транзакций, блокировок СУБД и неоптимальной архитектуре запросов. Рассмотрим эту проблему подробно, разберем правильные методы контроля остатков и выясним, как заставить документ работать быстро.
Когда мы пишем код в процедуре ОбработкаПроведения, всё выполнение происходит внутри одной неявной транзакции. Установка параметра Отказ = Истина не прерывает выполнение кода мгновенно. Это лишь сигнал платформе, что по завершении процедуры транзакцию нужно не зафиксировать (Commit), а откатить (Rollback).
Проанализируем ситуацию: если до момента установки отказа программа успела выполнить тяжелые операции (например, записать тысячи строк в регистры или создать множество временных таблиц), то при выполнении команды ROLLBACK серверу СУБД (SQL Server или PostgreSQL) требуется время и ресурсы, чтобы вернуть данные в исходное состояние. Если в этот момент база зависает, это может означать следующее:
Первое, на что стоит обратить внимание в коде автора — это выполнение запроса внутри цикла. Рассмотрим, как делать нельзя:
Для Каждого Позиция Из ТЧНоменклатура Цикл
// Это убивает производительность!
Проверка = ОбщиеФункции.ПроверкаОстаткаНаСкладе(Позиция.Номенклатура, Позиция.Склад);
Если Проверка Тогда
Отказ = Истина;
Прервать;
КонецЕсли;
КонецЦикла;
Если в документе 100 строк, система сделает 100 отдельных обращений к серверу БД. Это создает колоссальную нагрузку. Правильный подход — получить все данные одним запросом, используя временные таблицы. Перепишем процедуру ОбработкаПроведения, используя современную методику:
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
// 1. Подготовим движения
Движения.РегистрХранения.Записывать = Истина;
Движения.РегистрХранения.Очистить();
// 2. Создадим запрос для получения данных из ТЧ и остатков одновременно
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| СписаниеТовары.Номенклатура КАК Номенклатура,
| СписаниеТовары.Склад КАК Склад,
| СУММА(СписаниеТовары.Количество) КАК КоличествоДокумент
|ПОМЕСТИТЬ ВТ_Товары
|ИЗ
| Документ.СписаниеНоменклатуры.Номенклатура КАК СписаниеТовары
|ГДЕ
| СписаниеТовары.Ссылка = &Ссылка
|ГРУППИРОВАТЬ ПО
| СписаниеТовары.Номенклатура,
| СписаниеТовары.Склад
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Товары.Номенклатура КАК Номенклатура,
| ВТ_Товары.Склад КАК Склад,
| ВТ_Товары.КоличествоДокумент КАК КоличествоДокумент,
| ЕСТЬNULL(РегистрХраненияОстатки.КоличествоОстаток, 0) КАК КоличествоОстаток
|ИЗ
| ВТ_Товары КАК ВТ_Товары
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.РегистрХранения.Остатки(&МоментВремени,
| (Номенклатура, Склад) В (ВЫБРАТЬ Номенклатура, Склад ИЗ ВТ_Товары)) КАК РегистрХраненияОстатки
| ПО ВТ_Товары.Номенклатура = РегистрХраненияОстатки.Номенклатура
| И ВТ_Товары.Склад = РегистрХраненияОстатки.Склад";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Запрос.УстановитьПараметр("МоментВремени", МоментВремени());
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
// Проверяем остаток
Если Выборка.КоличествоОстаток < Выборка.КоличествоДокумент Тогда
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Недостаточно товара " + Выборка.Номенклатура +
". Требуется: " + Выборка.КоличествоДокумент +
", в наличии: " + Выборка.КоличествоОстаток;
Сообщение.Сообщить();
Отказ = Истина;
КонецЕсли;
// Если отказа нет, формируем движения
Если Не Отказ Тогда
Движение = Движения.РегистрХранения.ДобавитьРасход();
Движение.Период = Дата;
Движение.Номенклатура = Выборка.Номенклатура;
Движение.Склад = Выборка.Склад;
Движение.Количество = Выборка.КоличествоДокумент;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Проанализируем ситуацию, когда точка останова в ОбработкаПроведения не срабатывает. Это часто случается в клиент-серверных версиях. Чтобы «поймать» ошибку, нужно проверить следующее:
-debug.ЗаписьЖурналаРегистрации. Разставим «маячки» в коде, чтобы понять, на какой строке происходит зависание.
ЗаписьЖурналаРегистрации("ОтладкаПроведения", УровеньЖурналаРегистрации.Информация, , , "Начало проверки");
// Ваш код
ЗаписьЖурналаРегистрации("ОтладкаПроведения", УровеньЖурналаРегистрации.Информация, , , "Перед установкой Отказ = Истина");
Отказ = Истина;
ЗаписьЖурналаРегистрации("ОтладкаПроведения", УровеньЖурналаРегистрации.Информация, , , "Конец процедуры");
Если в журнале есть запись «Перед установкой Отказ», но нет «Конец процедуры», значит, проблема в системных механизмах отката транзакции или в подписках на события ПриЗаписи.
Выясним еще одну важную деталь. В ходе обсуждения выяснилось, что в регистре остатков использовалось слишком много измерений (например, Штрихкод). Важно помнить: в измерения регистра остатков нужно выносить только те данные, в разрезе которых нам действительно нужен остаток.
Если мы добавим Штрихкод в измерения, система будет хранить остатки по каждой комбинации «Номенклатура + Склад + Штрихкод». Если штрихкодов много, таблица итогов регистра «распухнет», и любой запрос к ней (особенно при откате транзакции) будет вешать базу.
Рассмотрим рекомендации по проектированию:
Номенклатура и Склад.Количество.ПричинаСписания или Штрихкод (если он нужен только для истории, а не для остатка).Рассмотрим более продвинутый способ, который рекомендует фирма «1С» для высоконагруженных систем. Вместо того чтобы сначала проверять остатки, а потом записывать, мы сначала записываем движения, а потом проверяем, не ушли ли мы в «минус».
Движения.РегистрХранения.Записывать = Истина;.Движения.Записать(); — это принудительно сбросит данные в базу внутри транзакции.КоличествоОстаток < 0.Отказ = Истина.Этот метод работает быстрее, так как платформа оптимизирует запись и чтение данных в рамках одной транзакции, избегая лишних блокировок «до» записи.
Проанализировав все сообщения, можно сделать вывод: зависание было вызвано не просто строчкой Отказ = Истина, а совокупностью факторов: старой платформой и неоптимальным кодом, который заставлял СУБД выполнять огромную работу по откату множества мелких операций.
План действий для исправления:
ЗаписьЖурналаРегистрации для локализации «зависшего» участка.Используя эти методы, мы не только избавимся от зависания, но и сделаем систему значительно более отзывчивой и надежной.