Почему типовой запрос по партиям в УПП выполняется долго и как это исправить?

Программист 1С v8.3 (Обычные формы) Управленческий учет Промышленность, строительство и АПК
← На главную

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

В этой статье мы подробно разберем, почему типовой механизм партионного учета начинает тормозить, проанализируем влияние SQL-оптимизатора на выполнение запросов и рассмотрим конкретные шаги по исправлению ситуации.

Причина 1: Устаревание статистики SQL-сервера

Одной из наиболее частых причин «тормозов» является неактуальная статистика SQL-сервера по таблицам, участвующим в запросе. Оптимизатор СУБД строит план выполнения на основе данных о распределении значений в индексах. Если статистика устарела, SQL-сервер может выбрать крайне неэффективный план (например, Index Scan вместо Index Seek).

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

Для решения этой проблемы мы рекомендуем настроить регламентное задание на стороне SQL-сервера, которое будет обновлять статистику по ключевым таблицам партионного учета каждые 15–20 минут. Ниже приведем пример скрипта, который помогает значительно ускорить проведение:


USE [Имя_Вашей_Базы]
GO
-- Обновление статистики для регистра сведений СписанныеТовары
UPDATE STATISTICS [dbo].[_InfoRg23857] -- Замените имя таблицы на ваше из консоли запросов или через получение структуры
GO
-- Обновление статистики для регистра накопления ПартииТоваровНаСкладах (основная таблица и таблица итогов)
UPDATE STATISTICS [dbo].[_AccumRg26367]
GO
UPDATE STATISTICS [dbo].[_AccumRgT26387]
GO
-- Обновление статистики для бухгалтерского учета партий
UPDATE STATISTICS [dbo].[_AccumRg26389]
GO
UPDATE STATISTICS [dbo].[_AccumRgT26415]
GO

Важно: Имена таблиц (например, _AccumRg26367) в каждой базе индивидуальны. Чтобы узнать точное имя таблицы, используйте метод ПолучитьСтруктуруХраненияБазыДанных() в 1С или посмотрите план запроса в SQL Profiler.

Причина 2: Проблема Parameter Sniffing (подбор параметров)

Проанализируем поведение системы: почему первый раз документ проводится 5 минут, а второй — 1 секунду? Это классическое проявление механизма Parameter Sniffing. При первом выполнении запроса SQL-сервер компилирует план выполнения, ориентируясь на конкретные значения параметров (например, на конкретную организацию или склад). Если для первого документа этот план был оптимальным, то для следующего (с другим набором данных) он может оказаться губительным.

В типовом коде УПП запросы часто строятся динамически, и использование временных таблиц несколько смягчает проблему, но не устраняет её полностью. Если обновление статистики не помогает на 100%, стоит рассмотреть возможность очистки процедурного кэша для конкретного запроса или использование расширений для добавления хинтов OPTION(RECOMPILE), однако это требует вмешательства на уровне СУБД или использования специализированных инструментов.

Причина 3: Неоптимальные условия в соединениях (SARGability)

Разберем структуру типового запроса, который привел автор темы. Обратите внимание на блок ПО при соединении таблицы СписанныеТовары и временной таблицы ПартииТоваровНаСкладах:


ВНУТРЕННЕЕ СОЕДИНЕНИЕ ПартииТоваровНаСкладах КАК ПартииТоваровНаСкладах
ПО СписанныеТовары.Номенклатура = ПартииТоваровНаСкладах.Номенклатура
    И СписанныеТовары.ХарактеристикаНоменклатуры = ПартииТоваровНаСкладах.ХарактеристикаНоменклатуры
    И (ВЫБОР
        КОГДА ПартииТоваровНаСкладах.Качество = &ПустоеКачество
            ТОГДА ИСТИНА
        ИНАЧЕ ...
       КОНЕЦ)

Использование конструкции ВЫБОР (в SQL это CASE) непосредственно внутри условия соединения — это крайне плохая практика для производительности. Такие условия делают запрос «не-SARGable» (Search Argumentable), то есть SQL-сервер не может эффективно использовать индексы для выполнения соединения. Вместо быстрого поиска он вынужден выполнять полное сканирование таблиц и вычислять условие для каждой пары строк.

Как оптимизировать код:

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

Причина 4: Вложенные запросы в параметрах виртуальных таблиц

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


ИЗ
    РегистрНакопления.ПартииТоваровНаСкладах.Остатки(
            &Дат,
            Организация = &Организация
                И (Номенклатура, ХарактеристикаНоменклатуры) В
                    (ВЫБРАТЬ
                        СписанныеТовары.Номенклатура,
                        СписанныеТовары.ХарактеристикаНоменклатуры
                    ИЗ
                        РегистрСведений.СписанныеТовары ... )

Хотя платформа 1С умеет оптимизировать конструкцию В (ВЫБРАТЬ ...), на больших объемах данных и сложных индексах это может привести к построению неоптимального плана. Оптимизатор может ошибиться в оценке количества строк, которые вернет подзапрос к СписанныеТовары.

Рекомендация: Сначала выберите список номенклатуры и характеристик из СписанныеТовары во временную таблицу (например, #ВТ_Фильтр), проиндексируйте её по ключевым полям, и только потом передавайте эту таблицу в качестве фильтра в виртуальную таблицу остатков. Это даст СУБД четкое понимание объема данных.

Причина 5: Избыточная индексация временных таблиц

В приведенном автором коде используется команда ИНДЕКСИРОВАТЬ ПО с перечислением огромного количества полей (Номенклатура, Характеристика, Качество, Склад, СтатусПартии, Заказ, Серия). Проанализируем ситуацию: во временной таблице всего 30–40 строк.

Создание индекса на SQL-сервере — это не «бесплатная» операция. Для таблицы из 40 строк создание сложного индекса может занимать больше времени, чем простое сканирование этой таблицы в памяти (Table Scan).

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

Итоги и план действий

Для исправления медленного выполнения партионных запросов мы рекомендуем следующий алгоритм:

  1. Проверьте актуальность итогов: Убедитесь, что итоги по регистру ПартииТоваровНаСкладах рассчитаны на актуальную дату (конец текущего месяца).
  2. Настройте обновление статистики: Внедрите SQL-скрипт для принудительного обновления статистики по таблицам регистров партий.
  3. Модифицируйте запрос: Если вы имеете возможность править типовой код (например, через расширение или в кастомизированной конфигурации), избавьтесь от ВЫБОР и ИЛИ в условиях соединений JOIN.
  4. Контролируйте регистр «Списанные товары»: Убедитесь, что в этом регистре не скапливается «мусор» от незавершенных транзакций, так как он активно участвует в фильтрации остатков.

Эти шаги позволят сократить время проведения документов с нескольких минут до считанных секунд, обеспечив стабильную работу системы даже при больших объемах данных.

← На главную