Быстродействие типовой 1С

13.06.24

База данных - HighLoad оптимизация

Оказывается, в типовых конфигурациях 1С есть, что улучшить!

Предисловие

Ниже моя работа за 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.

 

См. также

HighLoad оптимизация Программист Платформа 1С v8.3 Бесплатно (free)

Метод очень медленно работает, когда параметр приемник содержит намного меньше свойств, чем источник.

06.06.2024    9253    Evg-Lylyk    61    

44

HighLoad оптимизация Программист Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Анализ простого плана запроса. Оптимизация нагрузки на ЦП сервера СУБД используя типовые индексы.

13.03.2024    5090    spyke    28    

49

Инструментарий разработчика Программист Платформа 1С v8.3 Управляемые формы Система компоновки данных Конфигурации 1cv8 Абонемент ($m)

Отчет по регистрам Замеры времени с группировкой по дням или по часам.

1 стартмани

13.03.2024    621    2    vasilev2015    0    

3

Взаиморасчеты Бухгалтер Пользователь Платформа 1С v8.3 1С:Управление торговлей 11 Управленческий учет Абонемент ($m)

Отчет похож на Ведомость взаиморасчетов, но формируется быстро.

3 стартмани

13.03.2024    567    3    vasilev2015    0    

2

Инструментарий разработчика СКД Программист Платформа 1С v8.3 Управляемые формы Система компоновки данных Конфигурации 1cv8 Абонемент ($m)

Функция ПолучитьСтруктуруДанных() в отчете СКД для более удобного просмотра полей в терминах СУБД.

1 стартмани

11.03.2024    1325    7    vasilev2015    1    

1

HighLoad оптимизация Инструменты администратора БД Системный администратор Программист Платформа 1С v8.3 Конфигурации 1cv8 Абонемент ($m)

Обработка для простого и удобного анализа настроек, нагрузки и проблем с SQL сервером с упором на использование оного для 1С. Анализ текущих запросов на sql, ожиданий, конвертация запроса в 1С и рекомендации, где может тормозить.

2 стартмани

15.02.2024    12409    241    ZAOSTG    80    

115
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. ivanov660 4577 13.03.24 17:40 Сейчас в теме
Здорово было бы в каждом примере выделить абзацы - проблема, причина, решение. Так было бы более структурировано.
cleaner_it; +1 Ответить
4. vasilev2015 2722 13.03.24 20:07 Сейчас в теме
(1) Здравствуйте !

Да, хорошая идея. Лучше структурировано.
2. bakmistoff1977 36 13.03.24 18:11 Сейчас в теме
Для решения проблемы с быстродействием платформы я как-то написал Спячка1С.Управление индексированием. Инфостарт, естественно, не пропустил, опубликовано на Дзене. В принципе, должно закрывать тему. В 1С что-то подобное, но без аналитики, обещают в 8.3.26, не раньше. Может, вам будет интересно.
grumagargler; Painted; +2 Ответить
3. vasilev2015 2722 13.03.24 20:05 Сейчас в теме
Здравствуйте !

Посмотрел, вы сделали большую работу. Но к сожалению, не все проблемы решаются созданием индексов. Если злоупотреблять, размер индексов может превышать размер данных - разве это правильно? Официальная позиция 1С - не создавать произвольные индексы.
7. starik-2005 3087 14.03.24 16:21 Сейчас в теме
(3)
разве это правильно
Чем больше индексов - тем больше пишется в БД. Если записей в БД 5% от чтений - смысл имеет повысить скорость чтения за счет индексов. Если в БД пишется много новой инфы, то индексы - зло, лучше использовать отдельные регистры с собранными наборами - "витрины".
shard; user2041697; RustIG; +3 Ответить
9. grumagargler 726 14.03.24 17:06 Сейчас в теме
(3)
Если злоупотреблять, размер индексов может превышать размер данных - разве это правильно?

так это если злоупотлеблять, а если по чуть, и только если есть повод, то норм ;-)
10. bakmistoff1977 36 14.03.24 19:33 Сейчас в теме
(3) Индексы нужно делать под запросы - это азбука архитектуры БД. Платформа этого делать не позволяла до 26 версии, там делать свои индексы будет можно, по крайней мере, обещают. Не понятно, можно ли их будет удалять и редактировать. Так что в моём решении можно индексы как добавлять - необходимые, так и удалять - лишние. А лишних индексов - полно.
6. starik-2005 3087 14.03.24 16:19 Сейчас в теме
(2)
Спячка1С
Напишите вместо "1С" "OdinAss" - пропустит.
mistervoron; +1 Ответить
11. bakmistoff1977 36 15.03.24 14:10 Сейчас в теме
(6)
(6)Зачем же так Одина обижать. Один не осёл и не жопа. Да и администратор Инфостарта не пропустит.
5. Painted 49 14.03.24 14:39 Сейчас в теме
Слово "Оказывается" в подзаголовке умиляет. ))
zqzq; starik-2005; +2 Ответить
8. starik-2005 3087 14.03.24 16:22 Сейчас в теме
(5) "Нет пределов совершенству" vs. "Лучшее - враг хорошего".
12. user612295_death4321 16.03.24 14:29 Сейчас в теме
Удалось ли Вам пробить ворота вендора, передавая им подобные оптимизации?
14. vasilev2015 2722 18.03.24 09:19 Сейчас в теме
(12) Нет, даже не пытался. Лучше выложить в открытый доступ, чтобы каждый программист (в том числе и вендор) смог использовать эти исправления.
13. PerlAmutor 155 17.03.24 11:59 Сейчас в теме
Доработки связанные с производительностью неплохо укладываются когда это происходит на самописках или собственных доработках.

При доработке типовых конфигураций все изменения приходится потом поддерживать при обновлении релизов. Часто выполнять повторную оптимизации, когда запрос был перенесен в другой общий модуль или сильно переписан.

Бывают и плохие ситуации, когда запрос формирования движений переписан через расширение без аннотации "&ИзменениеИКонтроль" (&Вместо) и новые поля регистров при проведении документов просто молча не заполняются...

Оптимизация сделанная под одни данные в базе могут не работать на других данных и дать обратный эффект. Бывает, что за счет достижения скорости выполнения запроса увеличивается потребление памяти SQL сервером или его CPU, тогда это уже влияет на выполнение запросов других пользователей.

Иногда просто не остается никакого выбора и приходится проводить оптимизацию. Но делать оптимизацию типового когда, когда нет сильной необходимости - не стоит. При возможности, конечно стоит уведомить о проблеме разработчика типового решения, т.к. бывают запросы которые в 90% случаев будут работать медленно на любой конфигурации. Возможность протестировать эффект от оптимизированных запросов на различных базах есть у вендора. Если ему не удается воспроизвести проблему, то обычно просит копию базы, тогда вы вносите свой вклад в пополнение тестовых примеров.

В общем тема нужная, но с большим количеством нюансов, которые нужно учитывать. Возможно когда-нибудь в платформе появится искусственный интеллект, который по анализу данных в базе сможет типовые запросы адаптировать под SQL оптимизатор в автоматическом или полу-автоматическом режиме.
15. vasilev2015 2722 18.03.24 09:57 Сейчас в теме
(13) Сильная необходимость - наш случай. Пользователям неприятно ждать. Тоже люди.
16. RocKeR_13 1366 18.03.24 13:01 Сейчас в теме
У нас использовались Дополнительные реквизиты документа, из табличной части «ДополнительныеРеквизиты». Чтобы предотвратить запрос к табличной части документов, создал регистр сведений, содержащий значения Дополнительных реквизитов, заполняются при записи.

Так это же дополнительные сведения получаются тогда) Почему на них не перешли?
17. vasilev2015 2722 18.03.24 13:22 Сейчас в теме
(16) пользователям непривычно использовать в этом месте интерфейс дополнительных сведений и для удобства левого соединения в запросе динамического списка одна запись регистра - много значений свойств:

Измерение Документ.
Ресурсы Значение1, Значение2, Значение3.
18. RocKeR_13 1366 18.03.24 13:52 Сейчас в теме
(17)
одна запись регистра - много значений свойств

А как тогда по конкретному доп.реквизиту отбор сделать?
19. vasilev2015 2722 18.03.24 13:58 Сейчас в теме
Допреквизит "СтатусДокумента" дублируется в регистре Документ, СтатусДокумента, Значение2, Значение3 попадает в запрос динамического списка. Если нужно подробнее - пишите в личку.
20. Hellisad 88 05.07.24 15:57 Сейчас в теме
Типовые создаются именно для того, чтобы их оптимизировать под себя
Оставьте свое сообщение