Клиент обратился с проблемой долгого проведения документа Возврат ТМЗ от покупателя. В общем-то проблема старая, и известная, но до этого момента я никогда не углублялся в нее так далеко.
Клиент использует типовую конфигурацию Управление Торговым Предприятием для Казахстана, ред. 2.0 (Некий аналог КА для РФ, то есть по факту УТ+БП). Конфигурация была дописана, но незначительно, а сам документ и модули которые он использовал, были на поддержке и не изменены.
Хорошо, начал с первичного анализа ситуации, и замера производительности. База была большая, и создание и проведение документа Возврат ТМЗ от покупателя на 10 позиций заняло 669 секунд (повторное проведение документа заняло около 230 секунд) . Сказать что это много, значит ничего не сказать. Узким местом являлся запрос в общем модуле, который выполнялся 10 раз, но к этому мы еще вернемся:
Запрос был в функции общего модуля УправлениеЗапасамиПартионныйУчет.СформироватьТаблицуВозвращенныхПартий(). После детального осмотра, стало ясно, что функция ищет партии товара, списанные документом реализации, на основании которого и сделан возврат, для того чтобы вернуть товар по списанной себестоимости. Функция выполняется для каждой строки документа в цикле. Казалось бы, избавиться от запроса в цикле, и дело в шляпе, но не все так просто.
Решил пока оставить запрос в цикле, и посмотреть на сам запрос, который ищет эти партии. И тут я ужаснулся:
ВЫБРАТЬ РАЗЛИЧНЫЕ
СписанныеТовары.Регистратор КАК Регистратор
ПОМЕСТИТЬ ВтСписанныеТовары
ИЗ
РегистрСведений.СписанныеТовары КАК СписанныеТовары
ГДЕ
СписанныеТовары.Номенклатура = &Товар
И СписанныеТовары.ДокументПартии = &Регистратор
ИНДЕКСИРОВАТЬ ПО
Регистратор
;
//////////////////////////////////////////////////////////
ВЫБРАТЬ
СобственныйТовар.Партия КАК Партия,
СУММА(СобственныйТовар.СуммаСписания) КАК СуммаСписания,
СУММА(СобственныйТовар.Количество) КАК Количество,
СобственныйТовар.СчетУчета КАК СчетУчета,
0 КАК СуммаНДСПередачи,
0 КАК КоличествоПередачи,
1 КАК ЧислоСтатусПартии
ИЗ
(
ВЫБРАТЬ
ВЫБОР КОГДА ОборотыДтКт.СчетКт = Значение(ПланСчетов.Типовой.ТоварыПоПриходнымОрдерам)
ТОГДА ОборотыДтКт.СубконтоКт2
ИНАЧЕ NULL КОНЕЦ Как Партия,
ОборотыДтКт.СуммаОборот КАК СуммаСписания,
ОборотыДтКт.КоличествоОборотКт КАК Количество,
ОборотыДтКт.СчетКт КАК СчетУчета
ИЗ
РегистрБухгалтерии.Типовой.ОборотыДтКт(
,
,
Регистратор,
,
,
СчетКт В(&СчетКт),
,
СубконтоКт1 = &Товар) КАК ОборотыДтКт
ГДЕ
ОборотыДтКт.Регистратор = &Регистратор
И ОборотыДтКт.КоличествоОборотКт >= 0
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ОборотыДтКт.СубконтоДт2 КАК Партия,
- ОборотыДтКт.СуммаОборот КАК СуммаСписания,
- ОборотыДтКт.КоличествоОборотДт КАК Количество,
ОборотыДтКт.СчетДт КАК СчетУчета
ИЗ
РегистрБухгалтерии.Типовой.ОборотыДтКт(
,
&Период,
Регистратор,
СчетДт В (&СчетКт)
,
,
,
,
СубконтоДт1 = &Товар) КАК ОборотыДтКт
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВтСписанныеТовары КАК СписанныеТовары
ПО (СписанныеТовары.Регистратор = ОборотыДтКт.Регистратор)
ГДЕ
ОборотыДтКт.Регистратор <> &ЭтотВозврат
И ОборотыДтКт.СчетДт = ЗНАЧЕНИЕ(ПланСчетов.Типовой.ТоварыПоПриходнымОрдерам)
И ОборотыДтКт.КоличествоОборотДт >= 0
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ТоварыРеализованныеПоОрдерамБУ.Регистратор,
РеализацияТМЗ.Стоимость КАК СуммаСписания,
ТоварыРеализованныеПоОрдерамБУ.Количество,
ТоварыРеализованныеПоОрдерамБУ.СчетУчетаБУ
ИЗ
РегистрНакопления.ТоварыРеализованныеПоОрдерамБУ КАК ТоварыРеализованныеПоОрдерамБУ
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.РеализацияТМЗ КАК РеализацияТМЗ
ПО ТоварыРеализованныеПоОрдерамБУ.Регистратор = РеализацияТМЗ.Регистратор
И ТоварыРеализованныеПоОрдерамБУ.Номенклатура = РеализацияТМЗ.Номенклатура
ГДЕ
ТоварыРеализованныеПоОрдерамБУ.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Расход)
И ТоварыРеализованныеПоОрдерамБУ.ДокументСписания = &Регистратор
И ТоварыРеализованныеПоОрдерамБУ.Период < &Период
И ТоварыРеализованныеПоОрдерамБУ.Количество >= 0
И ТоварыРеализованныеПоОрдерамБУ.Номенклатура = &Товар) КАК СобственныйТовар
СГРУППИРОВАТЬ ПО
СобственныйТовар.Партия,
СобственныйТовар.СчетУчета
ИМЕЮЩИЕ
СУММА(СобственныйТовар.Количество) > 0
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
КомиссионныйТовар.Партия,
СУММА(КомиссионныйТовар.СуммаСписания),
СУММА(КомиссионныйТовар.Количество),
КомиссионныйТовар.СчетУчета,
СУММА(КомиссионныйТовар.СуммаНДСПередачи),
СУММА(КомиссионныйТовар.КоличествоПередачи),
2
ИЗ
(ВЫБРАТЬ
ОборотыДтКт.СубконтоКт3 КАК Партия,
ОборотыДтКт.СуммаОборот КАК СуммаСписания,
ОборотыДтКт.КоличествоОборотКт КАК Количество,
ОборотыДтКт.СчетКт КАК СчетУчета,
ТоварыПолученныеБУ.СуммаНДС КАК СуммаНДСПередачи,
ТоварыПолученныеБУ.Количество КАК КоличествоПередачи
ИЗ
РегистрБухгалтерии.Типовой.ОборотыДтКт(
,
,
Регистратор,
,
,
СчетКт = ЗНАЧЕНИЕ(ПланСчетов.Типовой.ТоварыПринятыеНаКомиссию),
&ВидыСубконтоКомиссия,
СубконтоКт1 = &Товар) КАК ОборотыДтКт
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыПолученныеБУ КАК ТоварыПолученныеБУ
ПО ОборотыДтКт.СубконтоКт3 = ТоварыПолученныеБУ.Партия
И ОборотыДтКт.СубконтоКт1 = ТоварыПолученныеБУ.Номенклатура
И ОборотыДтКт.Регистратор = ТоварыПолученныеБУ.Регистратор
ГДЕ
ОборотыДтКт.Регистратор = &Регистратор
И ОборотыДтКт.КоличествоОборотКт >= 0
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ОборотыДтКт.СубконтоДт3,
-ОборотыДтКт.СуммаОборот,
-ОборотыДтКт.КоличествоОборотДт,
ОборотыДтКт.СчетДт,
ТоварыПолученныеБУ.СуммаНДС,
ТоварыПолученныеБУ.Количество
ИЗ
РегистрБухгалтерии.Типовой.ОборотыДтКт(
,
&Период,
Регистратор,
СчетДт = ЗНАЧЕНИЕ(ПланСчетов.Типовой.ТоварыПринятыеНаКомиссию),
&ВидыСубконтоКомиссия,
,
,
СубконтоДт1 = &Товар) КАК ОборотыДтКт
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВтСписанныеТовары КАК СписанныеТовары
ПО (СписанныеТовары.Регистратор = ОборотыДтКт.Регистратор)
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыПолученныеБУ КАК ТоварыПолученныеБУ
ПО ОборотыДтКт.СубконтоДт3 = ТоварыПолученныеБУ.Партия
И ОборотыДтКт.СубконтоДт1 = ТоварыПолученныеБУ.Номенклатура
И ОборотыДтКт.Регистратор = ТоварыПолученныеБУ.Регистратор
ГДЕ
ОборотыДтКт.Регистратор <> &ЭтотВозврат
И ОборотыДтКт.КоличествоОборотДт >= 0) КАК КомиссионныйТовар
СГРУППИРОВАТЬ ПО
КомиссионныйТовар.Партия,
КомиссионныйТовар.СчетУчета
ИМЕЮЩИЕ
СУММА(КомиссионныйТовар.Количество) > 0
УПОРЯДОЧИТЬ ПО
ЧислоСтатусПартии,
Количество УБЫВ
Здесь использовались подзапросы (с которыми 1С не очень эффективно работает), запросы к виртуальной таблице Обороты регистра бухгалтерии с отбором по регистратору, соединения с регистрами накоплений, группировки. Запросы к виртуальным таблицам регистра бухгалтерии были толком без параметров, то есть факту, получалось так, что выбирались все обороты по товару и счету, и только потом делался отбор по регистратору в секции ГДЕ, что, собственно, было не оптимально. Также запросы учитывали использование ордерной схемы, и реализации товаров принятых на комиссию. У клиента не было ордерной схемы, комиссионного товара тоже, также себестоимость рассчитывалась по средней. Учитывая эти факты, было решено упростить запрос, в итоге получилось так:
ВЫБРАТЬ
NULL КАК Партия,
ОборотыДтКт.СуммаОборот КАК СуммаСписания,
ОборотыДтКт.КоличествоОборотКт КАК Количество,
ОборотыДтКт.СчетКт КАК СчетУчета,
0 КАК СуммаНДСПередачи,
0 КАК КоличествоПередачи,
1 КАК ЧислоСтатусПартии
ИЗ
РегистрБухгалтерии.Типовой.ОборотыДтКт(&Период, &Период, Регистратор, , , СчетКт В (&СчетКт), &МассивВидовСубконто, СубконтоКт1 = &Товар) КАК ОборотыДтКт
ГДЕ
ОборотыДтКт.Регистратор = &Регистратор
И ОборотыДтКт.КоличествоОборотКт >= 0
По факту остался запрос к виртуальной таблице регистра бухгалтерии, но, был добавлен параметр на период регистратора (то есть брались записи с отбором по дате документа реализации, и уже потом происходил отбор по регистратору), и параметр на субконто (особого прироста это не дало, но субконто желательно типизировать).Также была после дальнейшего анализа был оптимизирован запрос в процедуре ПроверитьКоличествоВозвратаПоРеализацииНУ(). Она выполняет контроль возврата по другому регистру бухгалтерии (Налоговый). Там изменений уже было не так много, добавлен параметр на период:
ВЫБРАТЬ
ОборотыДтКт.СуммаОборот КАК Сумма,
ОборотыДтКт.КоличествоОборотКт КАК Количество
ИЗ
РегистрБухгалтерии.Налоговый.ОборотыДтКт(
&Период, //Это было добавлено
&Период, //Это было добавлено
Регистратор,
,
,
СчетКт = &СчетКт,
&ВидыСубконто,
СубконтоКт1 = &Товар
И ВидУчетаКт = &ВидУчета) КАК ОборотыДтКт
ГДЕ
ОборотыДтКт.Регистратор = &Регистратор
И ОборотыДтКт.КоличествоОборотКт >= 0
УПОРЯДОЧИТЬ ПО
Количество УБЫВ
Для чистоты эксперимента созданный документ возврата был удален, и копией был создан новый. Также до этого момента были проведены другие возвраты и документы реализации. Вот сколько заняло времени проведение документа после оптимизации:
Предыдущий запрос, который занимал 90% времени проведения, теперь стал исполняться доли секунды (предпоследняя строка). Общая скорость проведения сократилась практически в 100 раз. Вот таким, нехитрым способом у меня получилось ускорить проведения документа Возврат ТМЗ от покупателя. База была файловая, и возможно, переход на SQL хоть как-то бы помог с этой проблемой, но для клиента это было бы дорого. Также еще можно было избавиться от запроса в цикле, но результат уже был достигнут хороший, и было принято решение остановиться пока что на этом.
Спасибо за внимание.