Как найти и исправить ошибки в неоптимальном коде обработки проведения документа в 1С?

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

Процедура ОбработкаПроведения — это сердце любого документа в системе 1С. Ошибки в этом модуле могут привести не только к замедлению работы всей системы, но и к повреждению данных, получению неверных остатков и "зависанию" транзакций. Проанализируем представленный код, который часто используется в качестве "задачи-ловушки" на собеседованиях. В нем заложено более 20 грубых нарушений стандартов разработки 1С. Разберем их по категориям и выясним, как написать этот код правильно.

Анализ исходного кода и структурные ошибки

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

  1. Отсутствие параметров запроса: В тексте запроса используются переменные &Период, &Контрагент и &Ссылка, однако в коде полностью отсутствует метод Запрос.УстановитьПараметр(). Это приведет к немедленной ошибке выполнения.
  2. Чтение данных из базы вместо объекта: Запрос обращается к таблице Документ.ПоступлениеТоваров.Товары. В момент проведения документа его данные уже находятся в оперативной памяти (в объекте ЭтотОбъект). Повторное чтение из базы данных — это лишняя нагрузка на SQL-сервер. Правильнее передавать табличную часть во временную таблицу.
  3. Использование Сообщить(): Согласно стандартам разработки, в процедурах проведения следует использовать объект СообщениеПользователю. Метод Сообщить "теряет" связь с объектом и не позволяет пользователю понять, к какой строке или реквизиту относится ошибка.
  4. Отсутствие флага Отказ: Даже если условие Количество > КоличествоОстаток выполняется и выводится сообщение об ошибке, переменная Отказ не устанавливается в значение Истина. В итоге документ будет проведен с некорректными данными.

Неоптимальная работа с запросами (SQL-уровень)

Проанализируем текст запроса. Здесь сосредоточены самые тяжелые с точки зрения производительности ошибки:

  1. Левое соединение с виртуальной таблицей: Соединение таблиц документа напрямую с виртуальной таблицей Остатки — это грубейшее нарушение. План запроса в этом случае будет крайне неоптимальным. Сначала данные документа нужно поместить во временную таблицу, проиндексировать их по измерениям (Номенклатура, Партия) и только потом соединять с остатками.
  2. Условие В (ВЫБРАТЬ...) в ГДЕ: В запросе присутствует избыточный подзапрос, который фильтрует те же самые данные, что уже выбраны в основной секции. Это создает двойную нагрузку на чтение табличной части документа.
  3. Использование конструкции ИТОГИ: В процедурах проведения использование ИТОГИ часто избыточно. Для целей проведения лучше использовать СГРУППИРОВАТЬ ПО, чтобы получить плоскую таблицу без дублей строк по номенклатуре и партии. Вложенные выборки (Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам)) только усложняют код и снижают читаемость.
  4. Отсутствие РАЗЛИЧНЫЕ: В основном запросе и подзапросе не используется ключевое слово РАЗЛИЧНЫЕ, что при наличии одинаковых строк в документе приведет к дублированию данных и неверному расчету сумм.

Критическая ошибка: Запросы в цикле (N+1)

Посмотрим на цикл обхода выборки. Здесь мы видим самую опасную ошибку для производительности — обращение к базе данных внутри итераций:


Пока ВыборкаПартия.Следующий Цикл
    СуммаВал = СуммаВал + РегистрыСведений.КурсыВалют.ПолучитьПоследнее(ВыборкаПартия.Партия.Date, ...);
    ...
КонецЦикла;

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

Также внутри цикла мы видим вызов: Справочники.Валюты.НайтиПоНаименованию("USD"). Это еще один скрытый запрос к БД на каждой итерации. Более того, поиск по наименованию (хардкод "USD") — это "плохой тон", так как в разных базах валюта может называться по-разному.

Логика проведения и запись движений

Разберем, как код работает с регистрами накопления:

  1. Неинициализированная переменная: Переменная СуммаВал начинает использоваться в выражении СуммаВал = СуммаВал + ... без предварительного обнуления. Если в коде выше она была определена, данные будут искажены.
  2. Отсутствие установки флага записи: Код добавляет записи в наборы движений (Движения.ТоварыНаСкладах.Добавить()), но нигде не указывает Движения.ТоварыНаСкладах.Записывать = Истина. В большинстве случаев это приведет к тому, что движения просто не сохранятся в базе.
  3. Проблема блокировок: В коде сначала проверяются остатки, а затем делается запись. В многопользовательской среде в микросекунду между проверкой и записью другой пользователь может списать этот же товар. Правильная стратегия: сначала записываем движения, потом проверяем остатки с использованием механизма управляемых блокировок.
  4. Период движений: Установка Движение.Период = Дата — это использование даты документа. Для оперативного проведения корректнее использовать МоментВремени(), чтобы гарантировать правильную последовательность записей в рамках одной секунды.
  5. Обращение через точку в цикле: Выражение ВыборкаНоменклатура.Номенклатура.ЕдиницаИзмерения вызывает неявный запрос к справочнику при каждом обращении. Все необходимые данные (ЕдиницаИзмерения, Дата Партии) должны быть получены в основном запросе через соединения.

Как оптимизировать решение?

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

  1. Подготовка данных: Выгружаем табличную часть Товары во временную таблицу, группируя по номенклатуре и партии и суммируя количество.
  2. Блокировка: Устанавливаем управляемую блокировку на те позиции номенклатуры, по которым собираемся проверять остатки.
  3. Единый запрос: Одним запросом получаем данные из временной таблицы, соединяем их с остатками по регистру ЗаказыПоставщикам и с курсами валют из регистра КурсыВалют.
  4. Запись и контроль: Сначала записываем движения в регистры, а затем проверяем — не ушли ли остатки в минус. Если ушли — устанавливаем Отказ = Истина.

Соблюдение этих правил позволит превратить неоптимальный код в надежный и быстрый механизм, соответствующий стандартам фирмы "1С".

← На главную