Всем доброго времени суток!
На днях столкнулся с задачей: долго рассчитывается себестоимость по организации за декабрь 2020 года (~40 минут), при этом в ноябре и всех прошлых месяцах она рассчитывалась ~10 минут.
Что имеем: платформа 8.3.16.1359, УПП 1.3 (1.3.152.3), PostgreSQL (тестировалось и на MS SQL), производственная организация, партионный учет, многопередельное производство.
Если вы столкнулись с такой же или похожей проблемой, то эта статья может натолкнуть на решение и сэкономить вам время.
Первым делом, следует выполнить пересчет итогов регистров - правда в контексте этой задачи, у меня ничего не изменилось.
Далее заходим в конфигуратор, запускаем режим отладки (F5) - запускается режим предприятия - находим нужный документ расчета себестоимости - возвращаемся в конфигуратор - активируем режим замера производительности:
возвращаемся в предприятие - запускаем проведение расчета себестоимости - ждем окончания.
После того, как расчет себестоимости завершен, возвращаемся в конфигуратор и останавливаем замер производительности:
на экране появится результат замера, подробно расшифровывать не будем, кому интересно можно почитать подробнее об этом инструменте https://its.1c.ru/db/metod8dev/content/1553/hdoc .
Итак, мой результат:
В глаза сразу бросается первая строка, мы видим, что данная строка кода выполняется 32 раза, 2 268,979787 секунд (~38 минут) и занимает 85,61% всего времени выполнения проведения расчета себестоимости.
Познакомимся ближе с этой строкой, дважды щелкаем на строку в замере производительности, попадем в модуль - в место выполнения этого кода, эта строка находится в общем модуле ПроцедурыРасчетаСебестоимостиВыпуска.
Ставим точку останова на данной строке кода:
Снова возвращаемся в предприятие и еще раз проводим расчет себестоимости, пока выполнение кода не остановится на нашей точке:
Выделяем ЗапросПоЗатратамНаВыпуск нажимаем Shift + F9:
теперь, у нас есть текст выполняемого запроса и его параметры, чтобы было удобно работать с этим запросом, нам необходимо, вернуться в пользовательский режим, запустить любую консоль запросов, скопировать туда текст запроса и заполнить параметры.
Кстати параметров очень много, да еще и массивы, очень удобно использовать подсистему инструменты разработчика, чем я и воспользуюсь, подробнее здесь //infostart.ru/public/15126/ .
У меня уже встроена эта подсистема и чтобы получить запрос из отладки со всеми параметрами в пользовательском режиме, мне достаточно нажать SHIFT + F9 и написать Отладить() , указав в качестве параметра свой запрос, далее нажать вычислить:
В режиме предприятия открывается консоль запросов, уже с заполненным текстом и параметрами, остается только выполнить:
Запрос возвращает 285 строк, за 195 секунд. Я решил проанализировать прошлые месяцы, меняя параметры периода в запросе и увидел, что действительно в прошлых месяцах количество строк было примерно в 3.5 раза меньше, (вспоминаем постановку задачи, выполнялось за 10 минут, стало за 40) действительно, пропорционально увеличению записей в регистре, увеличилось и время расчета себестоимости, к слову, записи увеличились обоснованно, из-за включения серий.
Собственно, с причиной разобрались, но неужели так и придется жить, ведь сейчас на предприятии даже не сезон, дальше будет еще дольше выполняться расчет, а один раз себестоимость редко проводится, далеко не всегда всё закрывается идеально с первого раза, сколько времени придется убить на закрытие месяца.
Это не устраивает.
Возвращаемся к нашему запросу и анализируем его:
в качестве основной используется таблица регистра накопления, к ней соединяются 4 вложенных запроса.
Вспоминаем методику:
Фирма 1С не рекомендует использовать вложенные запросы без особой потребности и предлагает заменять их временными таблицами или соединениями таблиц, замечая при этом, что результат такого изменения может быть другим. Такая рекомендация объясняется тем, что при использовании вложенных запросов оптимизатор СУБД не всегда может правильно определить размер выборки вложенного запроса и построить оптимальный план обращений к физическим таблицам базы данных, что сильно (иногда в десятки раз) может замедлить выполнение запроса.
А здесь, еще подробнее https://its.1c.ru/db/v8std/content/655/hdoc@51ff9ba0 .
Прислушаемся к совету, модернизируем запрос, заменив все вложенные запросы на временные таблицы, проиндексировав их по полях соединений(ниже будет итоговый текст запроса):
выполняем запрос в консоли:
видим, что скорость выполнения увеличилась со 195 секунд до 0.5 секунд!!! то, что надо, теперь внедрим его в конфигурацию и попробуем выполнить расчет себестоимости с замером производительности, для этого в общем модуле ПроцедурыРасчетаСебестоимостиВыпуска, находим функцию СформироватьТекстЗапросаПоЗатратамНаВыпуск(), и заменяем ее, на наш запрос с временными индексированными таблицами, типовой запрос опциональный - с использованием в тексте комментариев, которые потом в зависимости от параметров учета, заменяются на строки кода, наш запрос ничем не хуже и также будет опционален.
// Функция формирует текст запроса по регистру "Затраты на выпуск".
//
// Возвращаемое значение:
// Строка – Текст запроса
//
Функция СформироватьТекстЗапросаПоЗатратамНаВыпуск()
ТекстЗапроса =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| ВыпускПродукции.Подразделение КАК Подразделение,
| ВыпускПродукции.Продукция КАК Продукция,
| ВыпускПродукции.ХарактеристикаПродукции КАК ХарактеристикаПродукции,
| ВыпускПродукции.СерияПродукции КАК СерияПродукции,
| ВыпускПродукции.Спецификация КАК Спецификация,
| ВыпускПродукции.НоменклатурнаяГруппа КАК НоменклатурнаяГруппа,
| ВыпускПродукции.Заказ КАК Заказ,
| //ДляРеглУчета ВыпускПродукции.СчетУчетаНЗП КАК СчетУчетаНЗП,
| ВыпускПродукции.Заказ КАК Заказ1,
| ИСТИНА КАК ЕстьВыпускПродукции
|ПОМЕСТИТЬ ВТ_ВыпускПродукции
|ИЗ
| РегистрНакопления.ВыпускПродукции%СуффиксУчета% КАК ВыпускПродукции
|ГДЕ
| ВыпускПродукции.Период МЕЖДУ &НачДата И &КонДата
| И ВыпускПродукции.КодОперации <> &ВыпускПродукцииПоФиксированнойСтоимости
| //ДляРеглУчета И ВыпускПродукции.Организация = &Организация
|
|ИНДЕКСИРОВАТЬ ПО
| Подразделение,
| НоменклатурнаяГруппа,
| Продукция,
| ХарактеристикаПродукции,
| СерияПродукции,
| Спецификация,
| Заказ,
| СчетУчетаНЗП
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ИсключаемаяНоменклатура.Номенклатура КАК Номенклатура,
| ИсключаемаяНоменклатура.ХарактеристикаНоменклатуры КАК ХарактеристикаНоменклатуры,
| ИсключаемаяНоменклатура.СерияНоменклатуры КАК СерияНоменклатуры,
| ИСТИНА КАК ЕстьИсключаемаяНоменклатура
|ПОМЕСТИТЬ ВТ_ИсключаемаяНоменклатура
|ИЗ
| РегистрСведений.НоменклатураИсключаемаяИзБазыРаспределения%СуффиксОрганизаций% КАК ИсключаемаяНоменклатура
|ГДЕ
| ИсключаемаяНоменклатура.Период МЕЖДУ &НачДата И &КонДата
| И (&РасчетКосвенныхЗатрат
| ИЛИ &РасчетЗатратВстречногоВыпуска)
| //ДляРеглУчета И ИсключаемаяНоменклатура.Организация = &Организация
|
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура,
| ХарактеристикаНоменклатуры,
| СерияНоменклатуры
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| МИНИМУМ(Распределение.КорректировкаНЗП) КАК КорректировкаНЗП,
| МИНИМУМ(Распределение.ОприходованиеНЗП) КАК ОприходованиеНЗП,
| МИНИМУМ(Распределение.ВозвратИзНЗП) КАК ВозвратИзНЗП,
| МИНИМУМ(Распределение.ВстречныйВыпуск) КАК ВстречныйВыпуск,
| Распределение.Подразделение КАК Подразделение,
| Распределение.НоменклатурнаяГруппа КАК НоменклатурнаяГруппа,
| Распределение.СтатьяЗатрат КАК СтатьяЗатрат,
| Распределение.Затрата КАК Затрата,
| Распределение.ХарактеристикаЗатраты КАК ХарактеристикаЗатраты,
| Распределение.СерияЗатраты КАК СерияЗатраты
|ПОМЕСТИТЬ ВТ_Распределение
|ИЗ
| РегистрСведений.РаспределениеЗатратПоПеределам%СуффиксОрганизаций% КАК Распределение
|ГДЕ
| Распределение.Период МЕЖДУ &НачДата И &КонДата
| И Распределение.НомерПередела = &НомерПередела
| //ДляРеглУчета И Распределение.Организация = &Организация
|
|СГРУППИРОВАТЬ ПО
| Распределение.Подразделение,
| Распределение.НоменклатурнаяГруппа,
| Распределение.СтатьяЗатрат,
| Распределение.Затрата,
| Распределение.ХарактеристикаЗатраты,
| Распределение.СерияЗатраты
|
|ИНДЕКСИРОВАТЬ ПО
| Подразделение,
| Затрата,
| ХарактеристикаЗатраты,
| СерияЗатраты,
| НоменклатурнаяГруппа,
| СтатьяЗатрат
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВстречныйВыпускПродукции.Продукция КАК Продукция,
| ВстречныйВыпускПродукции.Затрата КАК Затрата,
| ВыпускПродукции.ХарактеристикаПродукции КАК ХарактеристикаЗатраты,
| ВыпускПродукции.СерияПродукции КАК СерияЗатраты,
| ИСТИНА КАК ВстречныйВыпуск
|ПОМЕСТИТЬ ВТ_РегистрВстречныйВыпуск
|ИЗ
| РегистрСведений.ВстречныйВыпускПродукцииУслуг.СрезПоследних(&КонДата, ) КАК ВстречныйВыпускПродукции
| ЛЕВОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ РАЗЛИЧНЫЕ
| ВыпускПродукции.Продукция КАК Продукция,
| ВыпускПродукции.ХарактеристикаПродукции КАК ХарактеристикаПродукции,
| ВЫБОР
| КОГДА ВыпускПродукции.Продукция.ВестиУчетПоСериямВНЗП
| ТОГДА ВыпускПродукции.СерияПродукции
| ИНАЧЕ ЗНАЧЕНИЕ(Справочник.СерииНоменклатуры.ПустаяСсылка)
| КОНЕЦ КАК СерияПродукции
| ИЗ
| РегистрНакопления.ВыпускПродукции%СуффиксУчета% КАК ВыпускПродукции
| ГДЕ
| ВыпускПродукции.Период МЕЖДУ &НачДата И &КонДата
| И ВыпускПродукции.КодОперации <> ЗНАЧЕНИЕ(Перечисление.КодыОперацийВыпускПродукции.ВыпускПродукцииПоФиксированнойСтоимости)
| //ДляРеглУчета И ВыпускПродукции.Организация = &Организация) КАК ВыпускПродукции
| ПО ВстречныйВыпускПродукции.Затрата = ВыпускПродукции.Продукция
|ГДЕ
| ВстречныйВыпускПродукции.СпособОценкиСтоимости = ЗНАЧЕНИЕ(Перечисление.СпособыОценкиСтоимостиВстречногоВыпуска.ПоРасчетнойСтоимости)
| И НЕ ВыпускПродукции.Продукция ЕСТЬ NULL
|
|ИНДЕКСИРОВАТЬ ПО
| Продукция,
| Затрата,
| ХарактеристикаЗатраты,
| СерияЗатраты
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| //ДляРеглУчета ЗатратыНаВыпуск.Организация,
| //ДляРеглУчета ЗатратыНаВыпуск.СчетУчета,
|
| ЗатратыНаВыпуск.Подразделение,
| ЗатратыНаВыпуск.НоменклатурнаяГруппа,
| ЗатратыНаВыпуск.Продукция,
| ЗатратыНаВыпуск.ХарактеристикаПродукции,
| ЗатратыНаВыпуск.СерияПродукции,
| ЗатратыНаВыпуск.Спецификация,
| ЗатратыНаВыпуск.Заказ,
| ЗатратыНаВыпуск.ДокументВыпуска,
| ЗатратыНаВыпуск.КодОперации,
|
| ЗатратыНаВыпуск.СтатьяЗатрат,
| ЗатратыНаВыпуск.СтатьяЗатрат.СтатусМатериальныхЗатрат КАК СтатусМатериальныхЗатрат,
| ЗатратыНаВыпуск.Затрата,
|
| ЗатратыНаВыпуск.ХарактеристикаЗатраты,
| ЗатратыНаВыпуск.СерияЗатраты,
|
| ЗатратыНаВыпуск.НоменклатурнаяГруппаНЗП,
| ЗатратыНаВыпуск.ЗаказНЗП,
| ЗатратыНаВыпуск.ПодразделениеНЗП,
|
| ВЫБОР
| КОГДА ЗатратыНаВыпуск.ПодразделениеНЗП = &ПустоеПодразделение
| ТОГДА ЗатратыНаВыпуск.Подразделение
| ИНАЧЕ ЗатратыНаВыпуск.ПодразделениеНЗП
| КОНЕЦ КАК ПодразделениеСписанияНЗП,
|
| ВТ_Распределение.КорректировкаНЗП,
| ВТ_Распределение.ОприходованиеНЗП,
| ВТ_Распределение.ВозвратИзНЗП,
|
| ЕСТЬNULL(ВТ_Распределение.ВстречныйВыпуск, ЛОЖЬ) КАК ВстречныйВыпуск,
| ЕСТЬNULL(ВТ_РегистрВстречныйВыпуск.ВстречныйВыпуск, ЛОЖЬ) КАК ЗатратаВстречногоВыпуска,
| ЕСТЬNULL(ВТ_ИсключаемаяНоменклатура.ЕстьИсключаемаяНоменклатура, ЛОЖЬ) КАК ЕстьИсключаемаяНоменклатура,
|
| ЕСТЬNULL(ВТ_ВыпускПродукции.ЕстьВыпускПродукции, ЛОЖЬ) КАК ЕстьВыпускПродукции,
|
| //ДляНалУчета СУММА(ЗатратыНаВыпуск.ПостояннаяРазница) КАК ПостояннаяРазница,
| //ДляНалУчета СУММА(ЗатратыНаВыпуск.ВременнаяРазница) КАК ВременнаяРазница,
| СУММА(ЗатратыНаВыпуск.Сумма) КАК Стоимость,
| СУММА(ЗатратыНаВыпуск.Количество) КАК Количество
|
|ИЗ
| РегистрНакопления.ЗатратыНаВыпускПродукции%СуффиксУчета% КАК ЗатратыНаВыпуск
|
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Распределение КАК ВТ_Распределение
| ПО ЗатратыНаВыпуск.Подразделение = ВТ_Распределение.Подразделение
| И ЗатратыНаВыпуск.Затрата = ВТ_Распределение.Затрата
| И ЗатратыНаВыпуск.ХарактеристикаЗатраты = ВТ_Распределение.ХарактеристикаЗатраты
| И ЗатратыНаВыпуск.СерияЗатраты = ВТ_Распределение.СерияЗатраты
| И ЗатратыНаВыпуск.НоменклатурнаяГруппа = ВТ_Распределение.НоменклатурнаяГруппа
| И ЗатратыНаВыпуск.СтатьяЗатрат = ВТ_Распределение.СтатьяЗатрат
|
| ЛЕВОЕ СОЕДИНЕНИЕ ВТ_РегистрВстречныйВыпуск КАК ВТ_РегистрВстречныйВыпуск
| ПО ЗатратыНаВыпуск.Продукция = ВТ_РегистрВстречныйВыпуск.Продукция
| И ЗатратыНаВыпуск.Затрата = ВТ_РегистрВстречныйВыпуск.Затрата
| И ЗатратыНаВыпуск.ХарактеристикаЗатраты = ВТ_РегистрВстречныйВыпуск.ХарактеристикаЗатраты
| И ЗатратыНаВыпуск.СерияЗатраты = ВТ_РегистрВстречныйВыпуск.СерияЗатраты
|
| ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ИсключаемаяНоменклатура КАК ВТ_ИсключаемаяНоменклатура
| ПО ЗатратыНаВыпуск.Продукция = ВТ_ИсключаемаяНоменклатура.Номенклатура
| И ЗатратыНаВыпуск.ХарактеристикаПродукции = ВТ_ИсключаемаяНоменклатура.ХарактеристикаНоменклатуры
| И ЗатратыНаВыпуск.СерияПродукции = ВТ_ИсключаемаяНоменклатура.СерияНоменклатуры
|
| ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ВыпускПродукции КАК ВТ_ВыпускПродукции
| ПО ЗатратыНаВыпуск.Подразделение = ВТ_ВыпускПродукции.Подразделение
| И ЗатратыНаВыпуск.НоменклатурнаяГруппа = ВТ_ВыпускПродукции.НоменклатурнаяГруппа
| И ЗатратыНаВыпуск.Продукция = ВТ_ВыпускПродукции.Продукция
| И ЗатратыНаВыпуск.ХарактеристикаПродукции = ВТ_ВыпускПродукции.ХарактеристикаПродукции
| И ЗатратыНаВыпуск.СерияПродукции = ВТ_ВыпускПродукции.СерияПродукции
| И ЗатратыНаВыпуск.Спецификация = ВТ_ВыпускПродукции.Спецификация
| И ЗатратыНаВыпуск.Заказ = ВТ_ВыпускПродукции.Заказ
| //ДляРеглУчета И ЗатратыНаВыпуск.СчетУчета = ВТ_ВыпускПродукции.СчетУчетаНЗП
|
|ГДЕ
| ЗатратыНаВыпуск.Период МЕЖДУ &НачДата И &КонДата
| И ЗатратыНаВыпуск.КодОперации В(&КодыОпераций)
|
| //ДляРеглУчета И ЗатратыНаВыпуск.Организация = &Организация
| //ДляНалУчета И ЗатратыНаВыпуск.СтатьяЗатрат.СтатусМатериальныхЗатрат <> &СтатусМатериальныхЗатратПринятые
| //ДляНалУчета И ЗатратыНаВыпуск.СчетУчета <> &СчетЕН
|
| И (ВТ_Распределение.КорректировкаНЗП
| И ЗатратыНаВыпуск.КодОперации В (&КодыОперацииКорректировка)
| ИЛИ НЕ ВТ_Распределение.КорректировкаНЗП
| И НЕ ЗатратыНаВыпуск.КодОперации В (&КодыОперацииКорректировка))
|
| И (ВТ_Распределение.ВозвратИзНЗП
| И ЗатратыНаВыпуск.КодОперации В (&КодыОперацииВозврат)
| ИЛИ НЕ ВТ_Распределение.ВозвратИзНЗП
| И НЕ ЗатратыНаВыпуск.КодОперации В (&КодыОперацииВозврат))
|
| И (ВТ_Распределение.ОприходованиеНЗП
| И ЗатратыНаВыпуск.КодОперации В (&КодыОперацииОприходованиеНЗП)
| ИЛИ НЕ ВТ_Распределение.ОприходованиеНЗП
| И НЕ ЗатратыНаВыпуск.КодОперации В (&КодыОперацииОприходованиеНЗП))
|
|СГРУППИРОВАТЬ ПО
| //ДляРеглУчета ЗатратыНаВыпуск.Организация,
| //ДляРеглУчета ЗатратыНаВыпуск.СчетУчета,
| ЗатратыНаВыпуск.Подразделение,
| ЗатратыНаВыпуск.НоменклатурнаяГруппа,
| ЗатратыНаВыпуск.Продукция,
| ЗатратыНаВыпуск.ХарактеристикаПродукции,
| ЗатратыНаВыпуск.СерияПродукции,
| ЗатратыНаВыпуск.Спецификация,
| ЗатратыНаВыпуск.Заказ,
| ЗатратыНаВыпуск.ДокументВыпуска,
| ЗатратыНаВыпуск.КодОперации,
|
| ЗатратыНаВыпуск.СтатьяЗатрат,
| ЗатратыНаВыпуск.Затрата,
| ЗатратыНаВыпуск.ХарактеристикаЗатраты,
| ЗатратыНаВыпуск.СерияЗатраты,
|
| ЗатратыНаВыпуск.НоменклатурнаяГруппаНЗП,
| ЗатратыНаВыпуск.ЗаказНЗП,
| ЗатратыНаВыпуск.ПодразделениеНЗП,
|
| ВЫБОР
| КОГДА ЗатратыНаВыпуск.ПодразделениеНЗП = &ПустоеПодразделение
| ТОГДА ЗатратыНаВыпуск.Подразделение
| ИНАЧЕ ЗатратыНаВыпуск.ПодразделениеНЗП
| КОНЕЦ,
|
| ВТ_Распределение.КорректировкаНЗП,
| ВТ_Распределение.ОприходованиеНЗП,
| ВТ_Распределение.ВозвратИзНЗП,
|
| ЕСТЬNULL(ВТ_Распределение.ВстречныйВыпуск, ЛОЖЬ),
| ЕСТЬNULL(ВТ_РегистрВстречныйВыпуск.ВстречныйВыпуск, ЛОЖЬ),
| ЕСТЬNULL(ВТ_ИсключаемаяНоменклатура.ЕстьИсключаемаяНоменклатура, ЛОЖЬ),
|
| ЕСТЬNULL(ВТ_ВыпускПродукции.ЕстьВыпускПродукции, ЛОЖЬ)
|
|УПОРЯДОЧИТЬ ПО
| //ДляРеглУчета ЗатратыНаВыпуск.Организация,
| //ДляРеглУчета ЗатратыНаВыпуск.СчетУчета,
| ПодразделениеСписанияНЗП,
| ЗатратыНаВыпуск.НоменклатурнаяГруппаНЗП,
| ЗатратыНаВыпуск.ЗаказНЗП,
| ЗатратыНаВыпуск.СтатьяЗатрат,
| ЗатратыНаВыпуск.Затрата,
| ЗатратыНаВыпуск.ХарактеристикаЗатраты,
| ЗатратыНаВыпуск.СерияЗатраты,
| ЕСТЬNULL(ВТ_ИсключаемаяНоменклатура.ЕстьИсключаемаяНоменклатура, ЛОЖЬ) УБЫВ,
|
| ЗатратыНаВыпуск.Подразделение,
| ЗатратыНаВыпуск.НоменклатурнаяГруппа,
| ЗатратыНаВыпуск.Продукция,
| ЗатратыНаВыпуск.ХарактеристикаПродукции,
| ЗатратыНаВыпуск.СерияПродукции,
| ЗатратыНаВыпуск.Спецификация,
| ЗатратыНаВыпуск.Заказ,
| ЗатратыНаВыпуск.ДокументВыпуска,
| ЗатратыНаВыпуск.КодОперации";
Возврат ТекстЗапроса;
КонецФункции // СформироватьТекстЗапросаПоЗатратамНаВыпуск()
проводим расчет себестоимости, смотрим результаты замера:
видим, что наш запрос теперь на 4 строке по времени выполнения всех процедур расчета себестоимости,
так же выполняется 32 раза, но теперь за 26 секунд вместо 2 268,979787! и занимает всего 6.43% от общего расчета себестоимости, вместо 85,61%
Время выполнения этой строки увеличилось в 87 раз!
В заключении, еще хочется обратить ваше внимание на эти статьи по ускорению расчета себестоимости, возможно в вашем случае они будут более подходящими:
//infostart.ru/1c/articles/255469/
//infostart.ru/public/176644/
надеюсь, статья оказалась полезной, всем спасибо!