Итак, дана находящаяся в актуальном состоянии клиент-серверная УПП. Квартальный объем документов реализации у нас приличный, но далеко не рекордный — примерно около 40 тыс. реализаций товаров и услуг, правда, с тенденцией к увеличению. Пришлось открыть конфигуратор и взяться за изучение 1Сного кода. Результатами решил поделиться с сообществом, потому что вполне может быть, что у кого-то возникнет та же проблема либо в УПП, либо в УТ 10, где этот документ устроен аналогично.
Вскрытие показало, что грабли ожидали нас в модуле документа ФормированиеЗаписейКнигиПродаж, а более конкретно - в процедуре РаспределитьОплатыПоДеревуСФ. В данной процедуре выполняется отбор из таблицы РаспределенныеОплаты с помощью построителя запроса. Отбор несложный, но используются операции на больше/меньше:
Отбор = Построитель_РаспределенныеОплаты.Отбор;
Отбор.Добавить("СчетФактура");
Отбор.СчетФактура.Использование = Истина;
Отбор.Добавить("РаспределеннаяОплата");
Отбор.РаспределеннаяОплата.ВидСравнения = ВидСравнения.Больше;
Отбор.РаспределеннаяОплата.Значение = 0;
Отбор.РаспределеннаяОплата.Использование = Истина;
Таблица РаспределенныеОплаты в базах с большим объемом реализаций обычно не совсем мала — в моем случае она имела на март месяц пару десятков тысяч строк. Но главная проблема даже не в этом, а в том, что в дальнейшем этот отбор делается многократно в цикле с большим количеством итераций:
Для каждого СтрокаСФ Из Дерево_НДСНачисленный.Строки Цикл
НаличиеОплатыНеТребуется = (СтрокаСФ.Строки[0].МоментОпределенияНалоговойБазыНДС = МоментОпределения_ПоОтгрузке) Или Дата >= '20080101';
ТаблицаОплат.Очистить();
Отбор = Построитель_РаспределенныеОплаты.Отбор;
Отбор.СчетФактура.Значение = СтрокаСФ.СчетФактура;
Отбор.РаспределеннаяОплата.ВидСравнения = ?(СтрокаСФ.СуммаСНДС>0,ВидСравнения.Больше,ВидСравнения.Меньше);
Построитель_РаспределенныеОплаты.Выполнить();
Собственно, вот здесь и проявляются искомые «тормоза» - этот циклический фрагмент мог гоняться от получаса и дольше в зависимости от количества документов в течение месяца.
Сначала я попробовал решить вопрос «малой кровью», проиндексировав таблицу РаспределенныеОплаты, но совместно с построителем запроса никакого эффекта это не дало. Тогда было решено отказаться от построителя и переписать механизм с использованием метода НайтиСтроки. Предлагаю вашему вниманию переписанную часть процедуры с моими комментариями.
Процедура РаспределитьОплатыПоДеревуСФ(Дерево_НДСНачисленный, ТаблицаРезультатов, СписокСчетовФактур, РаспределенныеОплаты, ОтражатьВРеестре = Истина, ОтражатьВидНачисления = Ложь )
НДСНалоговыйПериод = Неопределено;
// Далее используются следующие комментарии
// [-] и дальнейшее комментирование - исходный фрагмент удален
// [+] {...} - новый фрагмент вставлен
// [-] VVP - удаляем подготовку построителя и его отбора
//Построитель_РаспределенныеОплаты = Новый построительЗапроса();
//Построитель_РаспределенныеОплаты.ИсточникДанных = Новый ОписаниеИсточникаДанных(РаспределенныеОплаты);
//
//// Подготовка структуры отбора
//Отбор = Построитель_РаспределенныеОплаты.Отбор;
//Отбор.Добавить("СчетФактура");
//Отбор.СчетФактура.Использование = Истина;
//Отбор.Добавить("РаспределеннаяОплата");
//Отбор.РаспределеннаяОплата.ВидСравнения = ВидСравнения.Больше;
//Отбор.РаспределеннаяОплата.Значение = 0;
//Отбор.РаспределеннаяОплата.Использование = Истина;
//
//Построитель_РаспределенныеОплаты.Порядок.Добавить("ДатаОплаты");
ТаблицаОплат = Новый ТаблицаЗначений();
ТаблицаОплат.Колонки.Добавить("ДокументОплаты");
ТаблицаОплат.Колонки.Добавить("ДатаОплаты",Новый ОписаниеТипов("Дата", , , Новый КвалификаторыДаты(ЧастиДаты.ДатаВремя)));
ТаблицаОплат.Колонки.Добавить("СуммаОплаты",Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15,2)));
// [-] VVP - удаляем ссылку на таблицу источника построителя
//ТаблицаИсточникаПостроителя = Построитель_РаспределенныеОплаты.ИсточникДанных.ИсточникДанных;
// [+] VVP - делаем свою "таблицу источника построителя" и индексируем ее {
ТаблицаИсточникаПостроителя = РаспределенныеОплаты.Скопировать ();
ВыборкаОплат = РаспределенныеОплаты.СкопироватьКолонки ();
ТаблицаИсточникаПостроителя.Индексы.Добавить ("СчетФактура");
// }
МоментОпределения_ПоОтгрузке = Перечисления.МоментыОпределенияНалоговойБазыНДС.ПоОтгрузке;
Для каждого СтрокаСФ Из Дерево_НДСНачисленный.Строки Цикл
ТаблицаОплат.Очистить();
Если УчетНДС.ДляСчетаФактурыНеТребуетсяОплата(СтрокаСФ.СчетФактура) Тогда
НаличиеОплатыНеТребуется = Истина;
Иначе
НаличиеОплатыНеТребуется = (СтрокаСФ.Строки[0].МоментОпределенияНалоговойБазыНДС = МоментОпределения_ПоОтгрузке) Или Дата >= '20080101';
// [-] VVP - удаляем запрос построителем
//Отбор = Построитель_РаспределенныеОплаты.Отбор;
//Отбор.СчетФактура.Значение = СтрокаСФ.СчетФактура;
//Отбор.РаспределеннаяОплата.ВидСравнения = ?(СтрокаСФ.СуммаСНДС>0,ВидСравнения.Больше,ВидСравнения.Меньше);
//
//Построитель_РаспределенныеОплаты.Выполнить();
//Если Построитель_РаспределенныеОплаты.Результат.Пустой() и не НаличиеОплатыНеТребуется и СтрокаСФ.СуммаСНДС >= 0 Тогда
// // Оплата не обнаружена
// Продолжить;
//КонецЕсли;
//
//ВыборкаОплат = Построитель_РаспределенныеОплаты.Результат.Выгрузить(ОбходРезультатаЗапроса.Прямой);
// [+] VVP - делаем свой "запрос" данных через НайтиСтроки и далее навигацией по строкам {
МассивСтрокОплатПоСФ = ТаблицаИсточникаПостроителя.НайтиСтроки (Новый Структура ("СчетФактура", СтрокаСФ.СчетФактура));
ВыборкаОплат.Очистить ();
Для Каждого СтрокаОплатыПоСФ Из МассивСтрокОплатПоСФ Цикл
Если ((СтрокаСФ.СуммаСНДС > 0) И (СтрокаОплатыПоСФ.РаспределеннаяОплата > 0)) или
((СтрокаСФ.СуммаСНДС <= 0) И (СтрокаОплатыПоСФ.РаспределеннаяОплата < 0)) Тогда
СтрокаВыборкиОплат = ВыборкаОплат.Добавить ();
ЗаполнитьЗначенияСвойств(СтрокаВыборкиОплат, СтрокаОплатыПоСФ);
КонецЕсли;
КонецЦикла;
ВыборкаОплат.Сортировать ("ДатаОплаты");
Если ВыборкаОплат.Количество()=0 и не НаличиеОплатыНеТребуется и СтрокаСФ.СуммаСНДС >= 0 Тогда
// Оплата не обнаружена
Продолжить;
КонецЕсли;
// }
СуммаКПогашению = СтрокаСФ.СуммаСНДС;
В
... и далее код от 1С без изменений.
Добавлю, что индексация таблицы по колонке СчетФактура в данном случае несколько увеличивает скорость поиска, но несущественно, а вот отказ от построителя в пользу метода НайтиСтроки дает огромную прибавку в скорости даже на неиндексированной таблице. В случае с моей УПП скорость выполнения процедуры без индексации таблицы увеличилась примерно в 10 раз — с 40 минут до 4, а с индексацией удалось добиться примерно 3 минут вместо 4. В конечном итоге я все же оставил индексацию таблицы в рабочем коде.