Проблема медленной работы типовых механизмов при создании платежных документов — классическая ситуация для крупных баз 1С:Управление торговлей 11 (или ERP). Часто программисты сталкиваются с тем, что ввод на основании Приходного кассового ордера (ПКО) из документа Реализация товаров и услуг (РТУ) занимает 12–15 секунд даже на мощном оборудовании. Рассмотрим подробнее, почему это происходит и как мы можем оптимизировать этот процесс, опираясь на практический опыт и технические особенности платформы.
Первым делом проанализируем ситуацию. Основной "тормоз" зарыт в процедуре ВзаиморасчетыСервер.ЗаполнитьРасшифровкуПлатежаПоЗаказуКлиента. Если заглянуть в замер производительности, мы увидим запрос, который соединяет физическую таблицу регистра накопления РасчетыСКлиентами с его же виртуальной таблицей Обороты. Проблема здесь в следующем:
ЛЕВОЕ СОЕДИНЕНИЕ между огромной физической таблицей движений и расчетной виртуальной таблицей, план запроса становится чрезмерно сложным. Система не всегда может эффективно использовать индексы.ЗаказКлиента (или ОснованиеПлатежа) имеет составной тип. При соединении по такому полю SQL Server часто выполняет сканирование индекса (Index Scan) вместо быстрого поиска (Index Seek), так как ссылка в базе хранится в нескольких колонках.РасчетыСКлиентами.ЗаказКлиента.Договор) порождает в итоговом SQL-запросе дополнительные LEFT JOIN с таблицами документов и справочников, что перегружает оптимизатор СУБД.Разберем самый эффективный способ ускорения — разрыв сложного запроса на несколько простых этапов с использованием временных таблиц. Основная идея: сначала выбрать только нужные нам записи из регистра во временную таблицу, а уже затем соединять этот ограниченный набор данных с другими таблицами.
Посмотрим на пример оптимизированного кода, который дает десятикратный прирост скорости:
// ЭТАП 1: Выбираем только необходимые движения во временную таблицу
ВЫБРАТЬ
РасчетыСКлиентами.ЗаказКлиента КАК ЗаказКлиента,
РасчетыСКлиентами.Период КАК Период,
РасчетыСКлиентами.АналитикаУчетаПоПартнерам КАК АналитикаУчетаПоПартнерам,
РасчетыСКлиентами.НомерСтроки КАК НомерСтроки,
РасчетыСКлиентами.Валюта КАК Валюта,
РасчетыСКлиентами.ФормаОплаты КАК ФормаОплаты,
РасчетыСКлиентами.КОплате КАК КОплате
ПОМЕСТИТЬ ТаблицаРС
ИЗ
РегистрНакопления.РасчетыСКлиентами КАК РасчетыСКлиентами
ГДЕ
РасчетыСКлиентами.Активность
И РасчетыСКлиентами.ЗаказКлиента В(&ЗаказКлиента)
И РасчетыСКлиентами.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход)
И РасчетыСКлиентами.КОплате > 0;
////////////////////////////////////////////////////////////////////////////////
// ЭТАП 2: Основной запрос с использованием подготовленной таблицы
ВЫБРАТЬ
РасчетыСКлиентами.ЗаказКлиента КАК ОснованиеПлатежа,
Аналитика.Партнер КАК Партнер,
ВЫБОР
КОГДА ЕСТЬNULL(РасчетыСКлиентами.ЗаказКлиента.Договор.СтатьяДвиженияДенежныхСредств, ЗНАЧЕНИЕ(Справочник.СтатьиДвиженияДенежныхСредств.PutayaСсылка)) = ЗНАЧЕНИЕ(Справочник.СтатьиДвиженияДенежныхСредств.ПустаяСсылка)
ТОГДА ВЫРАЗИТЬ(РасчетыСКлиентами.ЗаказКлиента.Соглашение КАК Справочник.СоглашенияСКлиентами).СтатьяДвиженияДенежныхСредств
ИНАЧЕ РасчетыСКлиентами.ЗаказКлиента.Договор.СтатьяДвиженияДенежныхСредств
КОНЕЦ КАК СтатьяДвиженияДенежныхСредств,
РасчетыСКлиентами.Период КАК Период,
РасчетыСКлиентами.Валюта КАК ВалютаВзаиморасчетов,
РасчетыСКлиентами.КОплате КАК СуммаПоГрафику,
ЕСТЬNULL(РасчетыСКлиентамиОбороты.КОплатеРасход, 0) + ЕСТЬNULL(РасчетыСКлиентамиОбороты.ОплачиваетсяОборот, 0) КАК СуммаОплаты
ИЗ
ТаблицаРС КАК РасчетыСКлиентами
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.РасчетыСКлиентами.Обороты(, , Период, ЗаказКлиента В (&ЗаказКлиента)) КАК РасчетыСКлиентамиОбороты
ПО РасчетыСКлиентами.ЗаказКлиента = РасчетыСКлиентамиОбороты.ЗаказКлиента
И РасчетыСКлиентами.Валюта = РасчетыСКлиентамиОбороты.Валюта
И РасчетыСКлиентами.АналитикаУчетаПоПартнерам = РасчетыСКлиентамиОбороты.АналитикаУчетаПоПартнерам
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.АналитикаУчетаПоПартнерам КАК Аналитика
ПО РасчетыСКлиентами.АналитикаУчетаПоПартнерам = Аналитика.КлючАналитики
И (&Организация = Аналитика.Организация ИЛИ &Организация = НЕОПРЕДЕЛЕНО)
УПОРЯДОЧИТЬ ПО
РасчетыСКлиентами.Период,
РасчетыСКлиентами.НомерСтроки
ИТОГИ ПО
ОснованиеПлатежа;
Проанализируем параметры виртуальной таблицы Обороты. Важно понимать, что если мы не укажем отбор внутри параметров виртуальной таблицы (в скобках), платформа попытается рассчитать обороты по всему регистру, а затем отфильтрует их по условию ПО. Это критическая ошибка производительности.
Всегда передавайте массив ссылок в параметры виртуальной таблицы. В примере выше мы используем ЗаказКлиента В (&ЗаказКлиента). Это позволяет СУБД выбрать данные только по конкретным заказам, что в разы сокращает объем чтений с диска.
Рассмотрим ситуацию с полем ЗаказКлиента. В УТ 11 это поле может содержать ссылки на множество типов документов. При соединении таблиц по такому полю СУБД генерирует тяжеловесные конструкции в SQL-запросе.
Совет: если вы заранее знаете тип заказа (например, работаете только с заказами клиентов), используйте функцию ВЫРАЗИТЬ. Это явно укажет платформе, к какой физической таблице обращаться, и упростит план запроса. Кроме того, можно динамически формировать текст запроса, подставляя конкретный тип, что избавит СУБД от необходимости обрабатывать лишние типы данных.
Часто в этой же цепочке процессов тормозит другой запрос — ДенежныеСредстваСервер.РасшифровкаПлатежаНДС. В нем используется огромное количество операторов ОБЪЕДИНИТЬ ВСЕ по табличным частям разных документов (Акты, Реализации, Заказы и т.д.).
Чтобы ускорить этот узел, рекомендуется:
ОБЪЕДИНИТЬ ВСЕ делать соединение с этой ВТ.Проанализируем влияние прав доступа. Если в базе включен RLS (ограничение доступа на уровне записей) по организациям или партнерам, любой запрос к регистрам будет неявно дополняться условиями проверки прав. Это может превратить даже оптимизированный запрос в "тыкву".
Использование временных таблиц помогает и здесь: RLS отрабатывает один раз при заполнении ВТ, а последующие соединения выполняются по уже отфильтрованному набору данных, что значительно облегчает работу SQL-сервера.
Подведем итог: для ускорения ввода на основании в УТ 11 необходимо отказаться от прямых соединений физических таблиц регистров с виртуальными. Используйте временные таблицы для предварительной фильтрации данных, всегда указывайте отборы в параметрах виртуальных таблиц и не забывайте типизировать составные поля через ВЫРАЗИТЬ.