Предисловие
Ниже моя работа за 2020 год, результаты актуальны для типовой УТ 11.5.12.95 и многих современных конфигураций. Информация представлена фрагментами, загадками, намеками. Для получения правильных систематических знаний рекомендую "Подготовка к 1С:Эксперту по технологическим вопросам. Основной курс. Виктор Богачев."
Динамические списки
Обработка «ЖурналСкладскихАктов» использует динамический список документов СписокАктов, запрос «Выбрать * из РегистрСведений.РеестрДокументов». Если в списке установить сортировку по складу – работа замедляется.
Документы разных типов, реквизит «Склад» не индексирован. При создании формы пришлось сделать ограничение:
&НаСервере
Процедура ав_ПриСозданииНаСервереПосле(Отказ, СтандартнаяОбработка)
СписокАктов.КомпоновщикНастроек.Настройки.Порядок.Элементы.Очистить();
Месяц01 = НачалоМесяца( ТекущаяДата() );
Месяц31 = КонецМесяца( ТекущаяДата() );
ОбщегоНазначенияКлиентСервер.УстановитьПараметрДинамическогоСписка( СписокАктов, "НачалоПериода", Месяц01 );
ОбщегоНазначенияКлиентСервер.УстановитьПараметрДинамическогоСписка( СписокАктов, "КонецПериода", Месяц31 );
КонецПроцедуры
Если условия оформления сложные – работа замедляется. У нас использовались Дополнительные реквизиты документа, из табличной части «ДополнительныеРеквизиты». Чтобы предотвратить запрос к табличной части документов, создал регистр сведений, содержащий значения Дополнительных реквизитов, заполняются при записи. Добавил в запрос левым соединением. Уникальность записей обязательна. Не следует делать отборы или условия оформления по данным, которые не являются полями запроса.
Таких динамических списков в типовых конфигурациях очень много. Регулярное выражение для поиска медленной работы динамических списков в технологическом журнале ^.{13}([0-9]){8},CALL.*,Context=ДинамическийСписок
Расчет цены поставщика
Типовая УТ11, функция СформироватьЗапросТоварыДокументаЗакупки общего модуля ЗакупкиСервер возвращает текст запроса, фрагмент:
ЛЕВОЕ СОЕДИНЕНИЕ
РегистрСведений.ЦеныНоменклатурыПоставщиков.СрезПоследних(КОНЕЦПЕРИОДА(&Дата, ДЕНЬ),
ВидЦеныПоставщика В
(ВЫБРАТЬ
ВременнаяТаблицаДокументЗакупки.Соглашение.ВидЦеныПоставщика
ИЗ
ВременнаяТаблицаДокументЗакупки КАК ВременнаяТаблицаДокументЗакупки) И
(Номенклатура, Характеристика) В
(ВЫБРАТЬ
ВременнаяТаблицаТовары.Номенклатура,
ВременнаяТаблицаТовары.Характеристика
ИЗ
ВременнаяТаблицаТовары КАК ВременнаяТаблицаТовары)
) КАК ЦеныНоменклатурыПоставщиковСрезПоследних
Структура запроса не совпадает со структурой кластерного индекса регистра, отсутствует измерение «Партнер». Запрос выполняется с неоптимальным планом, создается огромная временная таблица, происходит переполнение tempdb. Необходимо добавить условие для поля «Партнер», чтобы запрос происходил по индексам. Проблемные запросы можно искать MS SQL - Стандартные отчеты - Производительность, запросы с наибольшим общим числом операций ввода вывода (но запросы в этом отчете не нормализованы) . Чтобы удобнее найти соответствие в структуре данных между полями СУБД и полями 1С, использовал отчет на основе функции ПолучитьСтруктуруДанных().
При соединении с таблицей СрезПоследних, будет лучше, если виртуальная таблица итоги среза существует физически, установлен флаг «Разрешить итоги срез последних». Если таблица формируется в запросе - в технологическом журнале можно найти текст AS MAXPERIOD.
Процедура ЗаполнитьВспомогательнуюИнформацию()
Модуль менеджера РегистрСведений.ВспомогательнаяИнформацияВзаиморасчетов. Запрос выполняется около 5 секунд при каждом изменении регистров взаиморасчетов. Используется левое соединение с неиндексированной временной таблицей, функция в условиях, затем условие ГДЕ, используется ОБЪЕДИНИТЬ.
ИЗ ИзмененияВспомогательнойИнформации КАК Изменения
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.РасчетыСКлиентами КАК Расчеты
ПО Изменения.РасчетныйДокумент = Расчеты.Регистратор
И Изменения.ДатаПлановогоПогашения = НАЧАЛОПЕРИОДА(
ВЫБОР КОГДА Расчеты.ДатаПлатежа = ДАТАВРЕМЯ(1,1,1)
ТОГДА Расчеты.ДатаРегистратора
ИНАЧЕ Расчеты.ДатаПлатежа
КОНЕЦ , ДЕНЬ)
И (Расчеты.Сумма <> 0 ИЛИ Расчеты.КОтгрузке <> 0 ИЛИ Расчеты.КОплате <> 0)
ГДЕ
ТИПЗНАЧЕНИЯ(Изменения.РасчетныйДокумент) <> ТИП(Документ.ПервичныйДокумент)
Внес исправление: Индексы, ВНУТРЕННЕЕ СОЕДИНЕНИЕ, ОБЪЕДИНИТЬ ВСЕ. Замена Левого соединения на Внутреннее.
Для проверки сделал контрольную выборку по РегистрСведений.ВспомогательнаяИнформацияВзаиморасчетов, изменил запрос в процедуре, отменил проведение документов – проконтролировал, что регистр пустой, провел документы – сравнил новую выборку с контрольной.
Было
",Rows=1,RowsAffected=-1,planSQLText="
1, 1, 1.5, 0, 0.0056, 157, 258, 1, |--Merge Join(Union)
0, 0, 1, 0, 1E-007, 157, 150, 1, |--Compute Scalar(DEFINE:([Expr1015]=CASE WHEN [Expr1003]=0
1, 1, 1, 0, 3.22, 168, 150, 1, | |--Stream Aggregate(GROUP BY:([T1].[_Q_000_F_000_TYPE],
0, 0, 5.36E+006, 0, 0.536, 168, 147, 1, | |--Compute Scalar(DEFINE:([Expr1071]=CASE
2, 1, 5.36E+006, 0, 22.4, 170, 146, 1, | |--Nested Loops(Left Outer Join, WHERE
1, 1, 1, 0.0113, 0.0001, 34, 0.0146, 1, | |--Sort(ORDER BY:([T1].[_Q_000_F
1, 1, 1, 0.00313, 0.000158, 34, 0.00328, 1, | | |--Table Scan(OBJECT:([
Стало
',Rows=1,RowsAffected=-1,planSQLText='
1, 1, 1.5, 0.0113, 0.000103, 158, 199, 1, |--Sort(DISTINCT ORDER BY:([Union1028] ASC, [Union1029]
1, 1, 2, 0, 2E-007, 158, 199, 1, |--Concatenation
0, 0, 1, 0, 1E-007, 158, 99.7, 1, |--Compute Scalar(DEFINE:([Expr1010]=substring([Expr1
1, 1, 1, 0, 5.3E-006, 170, 99.7, 1, | |--Stream Aggregate(GROUP BY:([T1].[_Q_000_F_0
1, 1, 8, 0.0113, 0.000138, 170, 99.7, 1, | |--Sort(ORDER BY:([T1].[_Q_000_F_000
1, 1, 8, 0, 0.0285, 170, 99.7, 1, | |--Parallelism(Gather Streams)
1, 8, 8, 0, 5.16, 170, 99.7, 1, | |--Hash Match(Partial Aggregate, HA
2, 8, 5.36E+006, 0, 10.3, 170, 94.5, 1, | |--Hash Match(Inner Jo
8, 8, 1, 0, 0.0285, 34, 0.0318, 1, | |--Parallelism(Distrib
1, 1, 1, 0.00313, 0.000158, 39, 0.00328, 1, | | |--Clust
Сравнение планов запроса «Было» и «Стало». Вместо nested loops используется hash match что снизило стоимость плана от 258 до 199. Проведение документов стало быстрее на 40%. Для просмотра замеры времени использовал отдельный отчет.
Ключевая операция |
07.02.2020 |
08.02.2020 |
||||
Время сред. |
Время сумма |
Количество |
Время сред. |
Время сумма |
Количество |
|
Провести и закрыть. документ. заказ поставщику. форма. форма документа |
9,3 |
1 106,9 |
119,0 |
5,3 |
1 074,8 |
203,0 |
Провести. документ. заказ поставщику. форма. форма документа |
8,5 |
804,8 |
95,0 |
5,1 |
486,2 |
95,0 |
Провести. документ. приобретение товаров услуг. форма. форма документа |
14,2 |
9 151,1 |
644,0 |
7,2 |
4 942,1 |
684,0 |
Документ Установка цен номенклатуры
При создании нового документа, программа предлагает рассчитать цены. Если в документе несколько цен имеют Способ задания цены – «Произвольный запрос к данным ИБ», то расчет может происходить медленно. Процедура ВычислитьЗначенияЦеныПоДаннымИБ(), модуль УстановкаЦенСервер вызывается в цикле, для каждого вида цен происходит запрос в строке кода ДанныеОтчета = ПроцессорВывода.Вывести(ПроцессорКомпоновки). Возможность рассчитать цены одним запросом без применения цикла ускорит работу.
При проведении цены регистрируются в плане обмена для передачи на весы и кассы. Процедура ЗарегистрироватьИзмененияДокумента() модуль ОбменДаннымиПодключаемоеОборудованиеOfflineСобытия,. Вызывается по подписке на событие проведения документа. Перенес регистрацию в плане обмена в фоновое задание. Фоновое задание не входит в транзакцию, поэтому перед запуском в транзакции проведения вносится запись в регистр, а при запуске фоновое задание проверяет, что предыдущие задания обработаны. Исправил запрос, условие ГДЕ располагалось после условий ВНУТРЕННЕГО СОЕДИНЕНИЯ что приводило к излишней выборке. Для просмотра замеры времени использовал отдельный отчет.
Ключевая операция |
18.02.2020 |
20.02.2020 |
||||
Время сред. |
Время сумма |
Количество |
Время сред. |
Время сумма |
Количество |
|
Провести и закрыть. документ. установка цен номенклатуры. форма. форма документа |
13,1 |
1 300,9 |
99,0 |
1,7 |
420,6 |
252,0 |
Функция АрхивнаяЗаписьСкладскогоЖурнала()
Общий модуль ИнтеграцияВЕТИС.
Реквизит «ИдентификаторВерсии» нужно индексировать, его значения более разнообразны.
ВЫБРАТЬ
ЗаписиСкладскогоЖурналаВЕТИС.Ссылка КАК Ссылка
ИЗ
Справочник.ЗаписиСкладскогоЖурналаВЕТИС КАК ЗаписиСкладскогоЖурналаВЕТИС
ГДЕ
ЗаписиСкладскогоЖурналаВЕТИС.АктуальнаяЗаписьСкладскогоЖурнала = &АктуальнаяЗаписьСкладскогоЖурнала
И ЗаписиСкладскогоЖурналаВЕТИС.ИдентификаторВерсии = &ИдентификаторВерсии";
Сводная ведомость расчетов «РасчетыСПартнерами»
Отчет с группировкой по регистратору не укладывается в целевое время. Рассчитывается остаток на каждый день. Описание расчета остатка на сайте ИТС https://its.1c.ru/db/v8316doc#bookmark:dev:TI000000627 https://its.1c.ru/db/metod8dev/content/3093/hdoc. Настройка отчета для расчета остатков задолженности на каждый день
Запускаем отчет на тестовом контуре, собираем технологический журнал
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns="http://v8.1c.ru/v8/tech-log">
<log location="D:\temp\Log2C" history="24">
<event>
<eq property="name" value="DBMSSQL"/>
<ge property="duration" value="10000"/>
<eq property="p:processName" value="ut11"/>
</event>
<event>
<eq property="name" value="CALL"/>
<ge property="duration" value="10000"/>
<eq property="p:processName" value="ut11"/>
</event>
<property name="all"/>
</log>
<plansql/>
</config>
Запросы на сервере СУБД (события DBMSSQL) выполняются около 2,5 секунд, затем завершается вызов сервера (событие CALL) 12,7 секунд, в том числе на сервере приложений 7,3 секунд.
17:02.112000-1358990,DBMSSQL,5,process=rphost…
17:02.690002-141000,DBMSSQL,6,process=rphost…
17:03.674000-967990,DBMSSQL,5,process=rphost…
17:04.034001-93999,DBMSSQL,6,process=rphost…
17:04.174000-124994,DBMSSQL,5,process=rphost…
17:12.893000-12734000,CALL,2,process=rphost,…,CpuTime=7359375
После получения данных от сервера СУБД, сервер приложений что-то считает 7,3 секунд. Сделаем другой отчет, рассчитаем остаток на каждый день на сервере СУБД. Используем регистры РасчетыСКлиентами, РасчетыСПоставщиками. Для исходного отчета это выполняется при &ДанныеПоРасчетам = 1. Вычисляем остатки на начало месяца. В списке документов соединением один-ко-многим добавляем движения более ранних документов. Таким образом, для каждого документа рассчитываем остаток задолженности. Поля отчета: «Сальдо начальное, Приход, Расход, Сальдо конечное». На тестовых данных, новый отчет отработал 3 секунды, исходный – 12,7 секунд. В целевое время новый отчет укладывается, заказчик доволен. Скачать отчет здесь.
Решение проблемы подходит для всех конфигураций, всех отчетов типа «Ведомость». Использование CPU сервера приложений видно в событии CALL. Механизм СКД расчета остатка на каждый день является более универсальным, но расчет на сервере СУБД быстрее.
Отсутствующие индексы
Системные таблицы missing_index* показывают отсутствующие индексы, которые могли бы упростить выполнение запросов. Создавать отсутствующие индексы не нужно, нужно разбираться. Смотрите подробнее //infostart.ru/1c/articles/1288706/
Например, если в базе данных не используются характеристики номенклатуры и в условиях запросов измерение Характеристика не используется, то типовая структура кластерных индексов многих регистров будет неоптимальной. После анализа отсутствующих индексов для регистров накопления и регистров сведений, измерение Характеристика можно передвинуть в конец списка.
Обратите внимание на Заметки из Зазеркалья
Приложение
Структура поля planSQLText в технологическом журнале.
The MS SQL Server query plan format
The planSQLText field for DBMS of Microsoft SQL Server consists of several records (lines). Each record consists of the following fields (in DBMS terms) in the order of description:
Rows
Executes
EstimateRows
EstimateIO
EstimateCPU
AvgRowSize
TotalSubtreeCost
EstimateExecutions
StmtText
The fields are separated by commas. The last field describing a query plan (StmtText) should be read to the end of the line ignoring the possible characters ",". The lines are separated by line breaks.