1с как посмотреть план запроса
Перейти к содержимому

1с как посмотреть план запроса

  • автор:

Описание и установка внешней обработки «Консоль запросов» для управляемого приложения

Инструмент «Консоль запросов» предназначена для отладки и просмотра результатов выполнения запросов в режиме 1С:Предприятие . Данная обработка предназначена в основном для разработчиков конфигураций и специалистов по внедрению. Данный инструмент можно использовать только в управляемом режиме. Если работа происходит в обычном режиме, то необходимо использовать «Консоль запросов» для 1C:Предприятия 8.1.

При разработке запросов в конфигураторе, как правило, требуется проводить отладку запроса на реальных данных. Данный инструмент позволяет вести разработку запроса (или пакета запросов) параллельно с просмотром результата. При работе с инструментом в толстом клиенте можно воспользоваться конструктором запросов, как и при работе в конфигураторе. Возможности по анализу результата запроса включают:

  • вывод данных временных таблиц;
  • замер времени выполнения запроса и числа строк;
  • подсветку указанных ячеек в результате запроса;
  • интерактивное сравнение двух результатов запроса (только в толстом клиенте);
  • вывод результата запроса в новом окне;
  • вывод плана выполнения запроса, а также SQL-текст запроса, сформированного в СУБД. Для СУБД Microsoft SQL Server план выполнения выводится в виде дерева, а для остальных СУБД – в текстовом формате технологического журнала. Для упрощения анализа запросов также предусмотрено два режима отображения текстов запросов: с именами таблиц и колонок СУБД или с именами объектов метаданных и реквизитов конфигурации (только в обработке для «1С:Предприятие» версии 8.3).

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

ВЫ МОЖЕТЕ ПРЯМО СЕЙЧАС УСТАНОВИТЬ ФАЙЛЫ ОТЧЕТОВ И ОБРАБОТОК
НА ЖЕСТКИЙ ДИСК ВАШЕГО КОМПЬЮТЕРА

Обработка КонсольЗапросов.epf для запуска в «1С:Предприятии» версии 8.2 находится в каталоге \1CITS\EXE\ExtReps\Unireps82\RequestConsoleManaged\

Обработка КонсольЗапросов.epf для запуска в «1С:Предприятии» версии 8.3 находится в каталоге \1CITS\EXE\ExtReps\Unireps83\RequestConsoleManaged\

Методика расследования проблем производительности на уровне работы СУБД PostgreSQL

Соотношение суммарного времени DBPOSTGRS к CALL показывает объем времени, занимаемого запросами, в общем времени вызовов.
Если доля превышает примерно 50%, то это повод разбираться со скоростью выполнения запросов.

Технологический журнал

Для того, чтобы получить запрос и его план, необходимо настроить сбор технологического журнала. Пример настройки:

ВНИМАНИЕ! Данный журнал может занимать значительный объем места на диске.

Настройку нельзя использовать на продуктивных серверах.

Только на серверах тестирования или разработки.

Чтобы получить план запроса, необходимо выполнить запрос непосредственно на сервере PostgreSQL с командой EXPLAIN .

Надо учитывать, что платформа «1С:Предприятие» устанавливает собственные параметры соединения с PostgreSQL. Их также необходимо установить перед выполнением запроса.

Рекомендуется создать файл с необходимыми командами и текстами запросов. Затем выполнить его из командной строки, перенаправив вывод в файл на диске.

Например, есть событие DBPOSTGRS из технологического журнала:

38:31.223006-1,DBPOSTGRS,5,process=rphost,p:processName=DATABASE. SessionID=2. dbpid=11988,Sql ,RowsAffected=1,Result=PGRES_TUPLES_OK,Context='Форма.Вызов : ВыполнитьЗапросСервер…

Создать файл с запросом:

$ vim query.sql
/* Параметры соединения платформы */ SET client_min_messages=error; SET lc_messages to 'en_US.UTF-8'; SET enable_mergejoin = off; SET escape_string_warning = off; SET cpu_operator_cost = 0.001; set client_encoding = 'utf8'; SET lock_timeout = 20000; EXPLAIN ANALYZE VERBOSE -- Получение актуального плана запроса SELECT T1._IDRRef, T1._Number, T1._Date_Time, T1._Fld30743RRef FROM _Document1337 T1 WHERE ((T1._Fld2830 = CAST(0 AS NUMERIC))) AND (T1._IDRRef = '\\200\\310p\\213\\315U\\034~\\021\\347\\025\\340@\\243\\3650'::bytea)
$ psql -U postgres --dbname=DATABASE --file=query.sql > query.sqlplan

Будет получен актуальный план запроса, идентичный платформенному.

$ cat query.sqlplan
Index Scan using _document1337_s_hpk on public._document1337 t1 (cost=0.06..8.07 rows=1 width=69) (actual time=0.011..0.011 rows=0 loops=1) Output: _idrref, _number, _date_time, _fld30743rref Index Cond: ((t1._fld2830 = '0'::numeric) AND (t1._idrref = '\x5c3230305c333130705c3231335c333135555c3033347e5c3032315c3334375c3032355c333430405c3234335c33363530'::bytea)) Planning Time: 2.585 ms Execution Time: 0.069 ms (5 rows)

Сложности возникают, когда исследуемый запрос использует временные таблицы. Тогда, необходимо собрать все запросы, которые заполняют эти таблицы (вверх по журналу от исследуемого запроса).

$ vim query.sql
SET client_min_messages=error; SET lc_messages to 'en_US.UTF-8'; SET enable_mergejoin = off; SET escape_string_warning = off; SET cpu_operator_cost = 0.001; set client_encoding = 'utf8'; SET lock_timeout = 20000; -- запрос использует временную таблицу, необходимо ее создать и заполнить перед его выполнением drop table if exists tt7 cascade; create temporary table tt7 (_Q_000_F_000RRef bytea ) without oids; drop index if exists tmpind_0; create index tmpind_0 on pg_temp.tt7(_Q_000_F_000RRef); INSERT INTO pg_temp.tt7 (_Q_000_F_000RRef) SELECT T1._IDRRef FROM _Document1017 T1 WHERE (T1._Fld2830 = CAST(0 AS NUMERIC)) ; ANALYZE pg_temp.tt7; EXPLAIN ANALYZE VERBOSE -- Получение актуального плана запроса SELECT (T1._Fld30691_TYPE || T1._Fld30691_RTRef), T1._IDRRef FROM _Document1337 T1 INNER JOIN pg_temp.tt7 T2 -- временная таблица ON ('\\010'::bytea = T1._Fld30691_TYPE AND '\\000\\000\\003\\371'::bytea = T1._Fld30691_RTRef AND T2._Q_000_F_000RRef = T1._Fld30691_RRRef) WHERE ((T1._Fld2830 = CAST(0 AS NUMERIC))) AND ((((T1._Fld30691_TYPE || T1._Fld30691_RTRef) <> '\\010\\000\\000\\004\\021'::bytea)))

Чтобы не собирать всю цепочку запросов вручную можно воспользоваться скриптом на языке 1С-Исполнитель getPGQueryWithTempTables.sbsl. После выполнения скрипт создаст файл (или выведет информацию в консоль), где соберет всю цепочку временных таблиц, необходимых для выполнения запроса.

Выгрузка данных для воспроизведения проблемы

Выгрузка только необходимых данных

Иногда необходимо передать разработчику «плохой» запрос и данные (для его выполнения), чтобы можно было воспроизвести проблему на другом сервере (например, под отладкой).

Передача всей базы данных затруднительна или невозможна.

Можно выгрузить только требуемые таблицы. Например, сам запрос и все временные таблицы строятся из таблиц _AccumRg1, _Reference2, _Reference3.

$ pg_dump -h server -U postgres -Fp -b -v -t _AccumRg1 -t _Reference2 -t _Reference3 -d ИМЯБАЗЫ -f /tmp/tables.backup

Однако, если цепочка временных таблиц слишком большая, или текст запроса содержит большое число таблиц, то проще получить их список с помощью скрипта. Для этого, необходимо взять файл с текстом «плохого» запроса и подготовкой всех временных таблиц, для его исполнения. А затем, выполнить скрипт:

$ cat bad_query.sql | grep -oP "(^|\s)_[a-zA-Z]+[0-9]+(_[a-zA-Z0-9]+)?" | grep -vP '^\s+_Fld.*' | grep -vP "_LineNo[0-9]+" | sort | uniq | tr -s '\n' '$' | sed 's/ //g' | sed 's/$$//' | sed 's/\$/ -t /g' | sed 's/\$$//g' | xargs -i echo pg_dump -h server -U postgres -Fp -O -t '<>' -f tables.backup [имя_базы]

Скрипт выведет текст команды pg_dump, для получения только необходимых данных.

Если, для сбора текста запроса, пользоваться скриптом getPGQueryWithTempTables.sbsl, то текст команды pg_dump будет в конце сформированного файла (или вывода консоли).

Для того, чтобы загрузить выгруженные таблицы в новую базу, необходимо создать новую базу на postgres с помощью 1С-Предприятия. В противном случае, будет ошибка при восстановлении таблиц из бэкапа.

$ /opt/1cv8/e2k/8.3.22.1851/1cv8 CREATEINFOBASE Srvr=server\;Ref=ИМЯБАЗЫ\;SchJobDn=Y\;SQLSrvr=СЕРВЕРБД\;DBMS=PostgreSQL\;DB=ИМЯБАЗЫ\;DBUID=postgres\;DBPwd=postgres\;CrSQLDB=Y\; $ psql -h server -U postgres -d ИМЯБАЗЫ -1 -f /tmp/tables.backup -v ON_ERROR_STOP=1

«Легкая» копия

Если необходимо выгрузить базу данных 1С:Предприятия, которая содержит:

  • полную структуру таблиц (без данных)
  • конфигурацию
  • часть таблиц с данными

То необходимо сначала выгрузить схему данных:

$ pg_dump -h server -U postgres --schema-only --dbname=ИМЯБАЗЫ --format=p > schema.sql

Затем, выгрузить данные необходимых таблиц.

$ pg_dump -h server -U postgres --format=p -b --dbname=ИМЯБАЗЫ --clean --if-exists -t _YearOffset -t Config -t ConfigCAS -t ConfigCASSave -t ConfigSave -t DBSchema -t DepotFiles -t Files -t IBVersion -t Params -t SchemaStorage | sed '/^users[.]usr/d' > data.sql

Восстановление базы выполняется в два шага:

$ psql -h server -U postgres --dbname=ИМЯБАЗЫ < schema.sql
$ psql -h server -U postgres --dbname=ИМЯБАЗЫ  data.sql

Если необходимо выгрузить полностью базу в простом формате, не разбивая на схему и данные, то это можно сделать так:

$ pg_dump -h server -U postgres -d ИМЯБАЗЫ-f database.sql -Fp

Восстановление базы выполняется в один шаг:

$ psql -U postgres -h server -d ИМЯБАЗЫ -f database.sql

Иногда бывает полезным выполнить загрузку в одной транзакции и с контролем наличия ошибок. Тогда будет остановка при первой ошибке:

$ psql -U postgres -h server -d ИМЯБАЗЫ -1 -f database.sql -v ON_ERROR_STOP=1

Журнал PostgreSQL

Запросы можно собирать непосредственно в журнале PosgreSQL.

$ psql
ALTER SYSTEM SET lc_messages = 'C'; -- все сообщения на английском языке
ALTER SYSTEM SET logging_collector = on; -- потребуется перезапуск сервера
ALTER SYSTEM SET log_directory = 'log';
ALTER SYSTEM SET log_min_duration_statement = 0; -- 0-все запросы. ВНИМАНИЕ. Сбор всех запросов может ухудшить производительность при высокой нагрузке.
ALTER SYSTEM SET log_duration = on;
$ перезапуск кластера любым способом (pg_ctl / pg_ctlcluster / systemd / service / . )

После этого в каталоге кластера, в папке «log», появится текстовый файл, который будет содержать в себе запросы PostgreSQL.

Преобразование журнала PostgreSQL к однострочному формату (каждое событие в одной строке):

$ cat postgresql.log | awk -vORS= '"$0;>'

Планы запросов в журнале PostgreSQL

Бывает, необходимо получать планы запросов в логе postgres. Например, при вставке во временную таблицу из таблицы значений, используется конструкция COPY … FROM STDIN.
Данные, которые вставляются во временную таблицу, нигде не логируются. Поэтому, сбор цепочки запросов по технологическому журналу не даст эффекта.

$ psql

Получить значение настройки shared_preload_libraries .

SELECT current_setting('shared_preload_libraries');

Дописать модуль auto_explain (потребуется перезапуска кластера).

ALTER SYSTEM SET shared_preload_libraries = '', '',…, 'auto_explain';
$ psql

Настройки модуля auto_explain

ALTER SYSTEM SET auto_explain.log_min_duration = '10s'; -- записывать в лог план для запросов >= 10 сек. 
ALTER SYSTEM SET auto_explain.log_analyze = true;
SELECT pg_reload_conf(); -- применение настроек

ВНИМАНИЕ! Автоматическое построение планов запросов снижает производительность системы.
Поэтому необходимо задавать параметр auto_explain.log_min_duration, получая планы только длительных запросов.

Отладочная информация в журнале PostgreSQL

Для получения более детальной информации о работе PostgreSQL можно включить сбор отладочных логов.

$ psql 
ALTER SYSTEM SET log_min_messages = 'debug2'; -- debug1. debug5
SELECT pg_reload_conf(); -- применение настроек

ВНИМАНИЕ! Включение данного журнала будет занимать значительный объем и может привести к замедлению СУБД.

Статистика исполнения запросов

Для расследования проблем может понадобится статистика по потреблению ресурсов запросами:

  • процессор
  • диск
  • буферный пул
  • количество исполнений
  • и т.д.

Для этого необходимо подключить модуль pg_stat_statements.

$ psql 
ALTER SYSTEM SET shared_preload_libraries = …, 'auto_explain', 'pg_stat_statements';
$ psql ALTER SYSTEM SET pg_stat_statements.max = 10000; ALTER SYSTEM SET pg_stat_statements.track = 'all'; SELECT pg_reload_conf(); -- применение настроек \с ИМЯ _БАЗЫ CREATE EXTENSION pg_stat_statements; SELECT pg_stat_statements_reset(); -- для сброса накопленной статистики..

Примеры полезных запросов

-- Нагрузка, создаваемая запросами SELECT pg_database.datname AS Database, pg_stat_statements.query AS Query, pg_stat_statements.calls AS ExecutionCount, pg_stat_statements.total_time ExecutionTime, pg_stat_statements.shared_blks_read + pg_stat_statements.shared_blks_written AS Memory, pg_stat_statements.local_blks_read + pg_stat_statements.local_blks_written AS IO, pg_stat_statements.temp_blks_read + pg_stat_statements.temp_blks_written AS Temp FROM pg_stat_statements AS pg_stat_statements INNER JOIN pg_database AS pg_database ON pg_database.oid = pg_stat_statements.dbid ORDER BY ExecutionTime DESC
-- Процент попадания в кэш SELECT round(100 * sum(blks_hit) / sum(blks_hit + blks_read), 3) as cache_hit_ratio FROM pg_stat_database;
-- Размер таблиц и индексов SELECT t.tablename, indexname, c.reltuples::integer AS num_rows, pg_size_pretty(pg_relation_size(quote_ident(t.tablename)::text)) AS table_size, pg_size_pretty(pg_relation_size(quote_ident(indexrelname)::text)) AS index_size, CASE WHEN x.is_unique = 1 THEN 'Y' ELSE 'N' END AS UNIQUE, idx_scan AS number_of_scans, idx_tup_read AS tuples_read, idx_tup_fetch AS tuples_fetched FROM pg_tables t LEFT OUTER JOIN pg_class c ON t.tablename=c.relname LEFT OUTER JOIN (SELECT indrelid, max(CAST(indisunique AS integer)) AS is_unique FROM pg_index GROUP BY indrelid) x ON c.oid = x.indrelid LEFT OUTER JOIN ( SELECT c.relname AS ctablename, ipg.relname AS indexname, x.indnatts AS number_of_columns, idx_scan, idx_tup_read, idx_tup_fetch,indexrelname FROM pg_index x JOIN pg_class c ON c.oid = x.indrelid JOIN pg_class ipg ON ipg.oid = x.indexrelid JOIN pg_stat_all_indexes psai ON x.indexrelid = psai.indexrelid ) AS foo ON t.tablename = foo.ctablename WHERE t.schemaname='public' ORDER BY pg_relation_size(quote_ident(indexrelname)::text) desc;
-- Отсутствие индексов SELECT relname, seq_scan - coalesce(idx_scan, 0) AS too_much_seq, case when seq_scan - coalesce(idx_scan, 0) > 0 THEN 'Нет индекса?' ELSE 'OK' END AS Message, pg_relation_size(relname::regclass) AS rel_size, seq_scan, coalesce(idx_scan, 0) AS idx_scan FROM pg_stat_all_tables WHERE schemaname='public' AND pg_relation_size(relname::regclass)>10000 -- ограничение на размер анализируемых таблиц ORDER BY too_much_seq DESC;
-- Использование буферного КЭШа (необходимо установить расширение pg_buffercache) CREATE EXTENSION pg_buffercache; SELECT 'total', pg_size_pretty(count(*) * (SELECT current_setting('block_size')::int)) FROM pg_buffercache UNION SELECT 'dirty', pg_size_pretty(count(*) * (SELECT current_setting('block_size')::int)) FROM pg_buffercache WHERE isdirty UNION SELECT 'clear', pg_size_pretty(count(*) * (SELECT current_setting('block_size')::int)) FROM pg_buffercache WHERE NOT isdirty UNION SELECT 'used', pg_size_pretty(count(*) * (SELECT current_setting('block_size')::int)) FROM pg_buffercache WHERE reldatabase IS NOT NULL UNION SELECT 'free',pg_size_pretty(count(*) * (SELECT current_setting('block_size')::int)) FROM pg_buffercache WHERE reldatabase IS NULL ;
-- Содержимое буферного пула SELECT c.relname, pg_size_pretty(pg_relation_size(c.oid)) as relation_size, pg_size_pretty(count(b.bufferid) * (SELECT current_setting('block_size')::int)) AS buffered_in_shared_buffers, round(100 * count(b.bufferid) * (SELECT current_setting('block_size')::int) / greatest(1,pg_relation_size(c.oid)),1) as pct_of_relation, round((100 * count(b.bufferid) / greatest(1,(SELECT setting FROM pg_settings WHERE name = 'shared_buffers')::decimal)),2) AS pct_of_shared_buffers FROM pg_class c INNER JOIN pg_buffercache b ON b.relfilenode = c.relfilenode AND b.reldatabase IN (0, (SELECT oid FROM pg_database WHERE datname = current_database())) -- WHERE c.relname = 'products' AND usagecount >= 2 – если нужен отбор по таблице GROUP BY c.oid, c.relname ORDER BY count(b.bufferid) * 8192 DESC LIMIT 10;

Запрос, который выполняется сейчас

Требуется разовая настройка:

$ psql 
ALTER SYSTEM SET track_activity_query_size = 1048576; -- храним тексты запросов размером до 1Мб.

Далее перезапускаем кластер, чтобы настройка применилась.

Теперь можно выполнять запрос:

$ psql SELECT datname, application_name, pid, now() - pg_stat_activity.query_start AS duration, query FROM pg_stat_activity WHERE state = 'active' ORDER BY duration DESC LIMIT 10;

Центр управления производительностью

Для расследования долгих запросов можно воспользоваться конфигурацией «Центр управления производительностью» из «Корпоративного инструментального пакета».

Для этого необходимо подключить исследуемую базу в режиме мониторинга.

Снять замеры и выполнить анализ.

Мониторинг ожиданий PostgreSQL

Если нет конкретного запроса, который приводит к замедлению системы (СУБД в целом работает медленно), то для расследования причин можно воспользоваться сбором статистики по ожиданиям.

Простой скрипт мониторинга текущих ожиданий:

$ vim ./wait_monitor.sh
#!/bin/bash while true do printf "\033c" psql -h server -U postgres -c "select wait_event_type, wait_event, count(*) as count from pg_stat_activity where state='active' group by wait_event_type, wait_event order by 3 desc;" sleep 3 done
$ chmod +x ./wait_monitor.sh
$ ./wait_monitor.sh

Cкрипт можно запустить в отдельном окне терминала и наблюдать в режиме реального времени, статистику по ожиданиям.
Данная статистика приблизительная, т.к. на экран выводится состояние по ожиданиям раз в 3 секунды. Все, что произошло за 3 секунды паузы – теряется.
Но для общей картины происходящего этого достаточно.

Затем необходимо взять ожидание, которое чаще всего находится на самом верху и разобраться в его причинах, воспользовавшись его описанием по ссылке https://www.postgresql.org/docs/current/monitoring-stats.html#WAIT-EVENT-TABLE

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

$ vim ./wait_monitor_csv.sh
#!/bin/bash while true do psql -h server -U postgres --no-align --csv --tuples-only -c "select now() as timestamp, wait_event_type, wait_event, count(*) as count from pg_stat_activity where state='active' group by wait_event_type, wait_event;" sleep 3 done
$ chmod +x ./wait_monitor_csv.sh
$ ./wait_monitor_csv.sh >> ./wait.log

В файл wait.log будут собраны снимки ожиданий.

Выполнить анализ собранных ожиданий можно с помощью psql.

$ psql 
create temp table waits(timestamp timestamp, wait_event_type varchar(100), wait_event varchar(100), count int);
COPY pg_temp.waits(timestamp, wait_event_type, wait_event, count) FROM '/home/user/wait.log' DELIMITER ',' CSV;
SELECT wait_event_type, wait_event, SUM(count) FROM pg_temp.waits GROUP BY wait_event_type, wait_event ORDER BY 3 DESC;

Загруженность оборудования

atop

$ sudo apt install atop

Режим интерактивного мониторинга

$ sudo atop

Cбор данных в файл. 1200 снимков с интервалом 3 секунды (1 час.)

$ sudo atop -w atop.log 3 1200

Если запущена служба atop, то счетчики оборудования снимаются постоянно. См.

$ sudo systemctl status atop 
Loaded: loaded (/lib/systemd/system/atop.service; enabled; vendor preset: enabled)
. . . . . . .
CGroup: /system.slice/atop.service
??6984 /usr/bin/atop -R -w /var/log/atop/atop_20221220 600

600 – это количество секунд между снимком информации о загруженности оборудования.

Частоту снятия счетчиков, службой, можно поменять

$ sudo vim /etc/default/atop
INTERVAl=60
$ sudo systemctl restart atop

Настройки журнала ежедневных счетчиков

$ sudo vim /usr/share/atop/atop.daily

Сбор данных в формате, пригодном для автоматической обработки (раз в секунду, до остановки по Ctrl+C)

$ sudo atop -P CPU,DSK,MEM,NET 1 > atop.csv

Преобразование бинарного файла в данные для автоматической обработки

$ atop -r /var/log/atop/atop_20221221 -P CPU,DSK,MEM,NET > atop.csv

Преобразование бинарного файла в текстовое представление для просмотра

$ atop -r /var/log/atop/atop_20221221 > atop.txt

Для визуализации atop можно написать «парсер», который преобразует собранные данные в формат (например csv).

Затем, загрузить эти данные в любое ПО для построения графиков.

Готовые средства, для просмотра графиков atop, можно найти в интернете по фразе «atop visualization».

nmon

Более продвинутое в визуальном смысле средство мониторинга — nmon.

$ sudo apt install nmon

Режим интерактивного мониторинга

$ sudo nmon
## (нажать C n d t u)

Если запустить в отдельной консоли монитор ожиданий PostgreSQL, то можно оценивать текущее состояние postgres и загрузки сервера.

Cбор данных в файл

$ nmon -f -s 2 -c 600 # 600 снимков с интервалом 2 секунды

Сформированный файл необходимо обработать для загрузки в ПО построения графиков.
Или воспользоваться готовыми средствами, которые можно найти по фразе «nmon visualizer».

Какой запрос нагружает оборудование

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

Найти его в технологическом журнале по полю dbpid.

53:32.515071-125295937,DBPOSTGRS,4,process=rphost,p:processName=erp,OSThread=42008,t:clientID=656,t:applicationName=1CV8C,t:computerName=server,t:connectID=13931,SessionID=190, Usr=Кладовщик документы_ТЦ000018,AppID=1CV8C,DBMS=DBPOSTGRS,DataBase=127.0.0.1\ERP,Trans=0,dbpid=167172,Sql=" SELECT T1._Fld836 FROM _InfoRg835 T1 LEFT OUTER JOIN _Document67 T2 ON T1._Fld847RRef = T2._IDRRef WHERE (T1._Fld839RRef IN ( VALUES('\\257,\\260\\260w\\260\\347SFg\\321\\311\\271E\\225\\026'::bytea), ('\\215\\000\\001\\342cX\\242?B\\237\\321\\355\\002\\242\\210\\237'::bytea), ('\\265\\257\\323\\2650-\\274\\016Dp\\211\\263\\277\\376\\026\\012'::bytea))) AND ((T1._Fld846 < '2022-11-21 00:00:00'::timestamp) OR (T1._Fld847RRef <>'\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000'::bytea) AND (T2._Date_Time IS NULL)) ",RowsAffected=1,Result=PGRES_TUPLES_OK,Context='

Или в логе postgres

2022-12-21 09:53:32.121 MSK [167172] postgres@ERP LOG: duration: 1251.295 ms bind : SELECT T1._Fld836 FROM _InfoRg835 T1 LEFT OUTER JOIN _Document67 T2 ON T1._Fld847RRef = T2._IDRRef WHERE (T1._Fld839RRef IN ( VALUES('\\257,\\260\\260w\\260\\347SFg\\321\\311\\271E\\225\\026'::bytea), ('\\215\\000\\001\\342cX\\242?B\\237\\321\\355\\002\\242\\210\\237'::bytea), ('\\265\\257\\323\\2650-\\274\\016Dp\\211\\263\\277\\376\\026\\012'::bytea))) AND ((T1._Fld846 < '2022-11-21 00:00:00'::timestamp) OR (T1._Fld847RRef <>'\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000'::bytea) AND (T2._Date_Time IS NULL))

Если запрос еще выполняется, то получить его из pg_stat_activity

$ psql 
SELECT datname, application_name, pid, now() - pg_stat_activity.query_start AS duration, query FROM pg_stat_activity WHERE state = 'active' and pid = 167172;

Смотрим запросы 1С через Microsoft SQL Profiler по следам ошибок разработчиков, приводящих к проблемам производительности

  • запуск профайлера.png
  • настройка трассировки.png
  • условия база данных.png
  • условия длительность.png
  • условие в тексте запроса.png
  • image.png
  • image.png
  • image.png
  • image.png
  • image.png
  • image.png
  • image.png
  • Настройка списка замеры.png
  • 2021-08-27_11-44-23.png
  • 2021-08-27_12-41-05.png
  • 2021-08-27_12-43-03.png
  • 2021-08-27_12-47-42.png
  • 2021-08-27_13-08-19.png
  • 2021-08-27_13-21-28.png
  • 2021-08-27_13-25-25.png
  • 2021-08-27_15-32-02.png
  • 2021-08-27_15-32-52.png
  • 2021-08-27_15-38-20.png
  • 2021-08-27_16-14-46.png
  • 2021-08-27_16-23-03.png
  • простой запрос пит кд.png
  • сложный запрос пит кд.png
  • 2021-08-28_18-35-33.png
  • пит кд слож RLS.png
  • пит кд RLS легк.png
  • 2021-08-28_18-47-21.png
  • структура регистра.png
  • ожидаются индексы.png
  • таблица.png
  • таблица старый.png
  • 2021-09-06_10-55-50.png
  • 2021-09-06_22-55-27.png
  • 2021-09-06_23-05-09.png
  • 2021-09-06_23-24-54.png
  • 2021-09-06_23-38-13.png
  • 2021-09-06_23-43-31.png

Это продолжение статьи про распространенные ошибки разработчиков, в данном случае посмотрим, как выглядит обработка рассмотренных в этой статье "запросов" сервером MS SQL запросов из 1С. Также рассмотрим некоторые другие интересные ситуации.

Мы не ставим целью описать все возможные нюансы и особенности поведения сервера, сделать из вас профессионала (для этого требуется гораздо больше времени и больше различных источников информации).

Мы хотим показать, как выглядит оптимизация запросов из статьи о распространенных ошибках разработчиков с точки зрения SQL сервера.

Дополнительно мы надеемся, что по результатам прочтения статьи вы сможете:

  1. Настроить и подключить профайлер
  2. Получить/увидеть схему плана запроса, технологические данные - длительность (duration), нагрузку на CPU и др.
  3. Сделать некоторую "грубую" оценку - оценить: стало лучше, не изменилось или хуже (в большинстве случаев этого достаточно).

Мы ориентируемся на специалистов начального и среднего уровня. Возможно, даже господа сеньоры смогут почерпнуть для себя что-то новое, по опыту общения с коллегами до публикации такие ситуации имели место.

I) Инструменты и подготовка.

SQL Server Profiler вам потребуется, чтобы понять, почему может тормозить запрос, проверить результаты оптимизации, когда анализа кода на языке 1С недостаточно. В большинстве случаев результирующий запрос выглядит монстром (почти 95% из-за RLS), и понять там что-либо можно с трудом. В этом случае вам может помочь SQL Sentry Plan Explorer, который поможет выделить самые жирные цепочки. Статей в интернете и на текущем ресурсе по этой тематике много или если вы уже знаете, как с ним обращаться, листайте дальше. Но для тех, кто не знает, это будет хорошим кратким описанием.

1. Начинаем и запускаем SQL Server Profiler.

1.1 Открываем Microsoft SQL Server Management Studio

1.2 Запускаем SQL Server Profiler и подключаемся к базе данных (вводим адрес, логин и пароль).

2. Настраиваем трассировку (события).

В открывшемся приложении сразу будет открыта форма настройки подключения. Если это не так, то нажмите "New trace . " (Новая трассировка) или "Ctrl+N".

2.1 На вкладке главное (General) смотрим настройки и сохраняем как шаблон, если нужно.

2.2 Переходим на вкладку события (Events Selection) и выполняем настройку захвата интересующих нас событий. Из выбранных по умолчанию оставляем RPC.Complated, остальные отключаем.

2.3 Добавляем еще следующие события из блока Performance:

  • Show plan XML Statistic Profile- схема плана запросов с данными статистики
  • ShowPlan Text (Unencoded) - текстовое представление запроса (не обязательно)
3 Ставим отборы в профайлере

3.1 Ставим отбор по базе данных. Устанавливаем фильтр на имя базы данных DatabaseName. Если не видно, то нажмите флаг "Show all columns" и выберите требуемую колонку.

3.2. Ставим отбор по длительности Duration. Этот параметр вы можете поставить, чтобы искать длительные запросы или отсеять всякие мелкие служебные. При анализе нужно руководствоваться соображениями - чем меньше этот показатель, тем лучше. Фактически - это время выполнения запроса.

3.3 Ставим отбор по тексту. Устанавливаем по слову в тексте TextData. Будем искать по вхождению некоторого ключа. Обязательно поместите его в "%" (т.е. вхождение по подобно). Подставлять ключевое слово в запрос 1С и искать это ключевое слово в запросе в профайлере - это довольно популярный и удобный подход, и мы будем его использовать.

3.4. Дополнительные информационные поля. Добавляем еще поля

  • RowCount – число записей, которые возвращает сервер.
  • Reads - количество чтений, сколько данных было прочитано.
  • Writes - количество записей, что было записано в нашем случае во временные таблицы.

3.5. Жмем кнопку "Run" (Запуск). Приложение можно сказать разделено на две части. В первой части таблица с основной информацией по запросу: Событие, Нагрузка CPU, Время выполнения (Duration) и т.д. По ним мы будем понимать/получать оценку в значениях/цифрах. Во второй части - будет приводиться информация по тексту запроса или плану запроса в графическом виде. Все картинки запросов по тексту ниже взяты из нее.

4. Запускаем SQL Sentry Plan Explorer

Как было сказано выше, этот инструмент будем использовать, когда план запроса сложный, чтобы выделить в нем самые жирные цепочки. Для выделения этих цепочек пользуйтесь фильтром "Filter" и изменяйте значение %. С увеличением этого параметра будут пропадать цепочки с "малым" вкладом в стоимость плана. Т.е. останутся только те, которые вносят самое большое влияние.

Давайте рассмотрим, как в него загрузить данные чуть более подробно:

  • кликнем левой кнопкой мышки на строке с событием "Show Plan XML" приложения MS SQL Profiler и в появившемся окне выберем опцию "Сохранить данные события. (Extract Event Data. )". Данные сохранятся в формате плана запроса "SQLPlan".

  • Далее в приложении Plan Explorer открываем этот файл через "Открыть (Open)". В результате в данные сохраненного ранее плана загрузятся, и у вас появится возможность просмотра в более удобном варианте.
5. Запускаем тестируемого клиента

5.1. Открываем базу данных на платформе 1С. В нашем случае - это конфигурация ЕРП, некоторая версия с набором данных за хороший период. Запускаемся под администратором или под пользователем с ограничениями RLS (не забудьте для этого пользователя разрешить интерактивное открытие отчетов и обработок). Как вариант - можете добавить обработку в расширение и подключить ее.

5.2. Открываем консоль запросов (любую консоль запросов). И переходим к примерам. Сами примеры это текстовое представление динамического списка на языке запросов 1С с включением некоторого ключа (параметр " КлючЗапроса "), по которому далее будет накладываться фильтр в профайлере TextData, т.ч. не забудьте его добавить. Еще раз - это значение будет ловиться фильтром TextData, поэтому убедитесь что значения фильтра и параметра "КлючЗапроса" совпадают.

II) Краткая информация про планы запросов

Прежде чем мы начнем смотреть, давайте введем несколько тезисов и рассмотрим краткую справку по операторам плана запросов. Мне понравилась статья на текущем ресурсе - "Зачем запросу план и кто его выполняет?"

Сначала давайте ответим на вопрос: Что же такое план запроса и зачем он нужен?

"План выполнения запроса — последовательность операций, необходимых для получения результата SQL-запроса в реляционной СУБД" - из Википедии

Т.е. на плане запроса мы видим, как SQL сервер получает и обрабатывает данные. И эта графическая (может, в формате структурированного текста) информация помогает нам выполнить оптимизацию. Однако, в рамках 1С нам доступен ограниченный набор операций по оптимизации, т.к. мы напрямую общаемся только с 1С.

  • Sort (Сортировка) - сортирует данные
  • Filter (Фильтр/Отбор) - фильтрует/отбирает данные согласно условию
  • Compute Scalar (Вычислить) - вычисляет выражение
  • Concatenation (Конкатенация) - объединяет все входные данные в один поток, в котором содержатся все данные (работает как оператор "ОБЪЕДИНИТЬ ВСЕ").
  • Merge Interval (Объединение различные интервалов) - объединяет интервалы, выбирает только не пересекающиеся данные, различные.
  • Select (Выбрать) - выбирает данные
  • Insert (Вставить) - создает временную таблицу
  • Key LookUp (Поиск ключа) - ищет ключ, если не хватает.
  • Table Scan (Сканирует таблицу) - ходит по всей таблице, плохо для большого количества данных.
  • Index Scan (Сканирует индекс) - считывает весь индекс.
  • Index Seek (Поиск по индексу) - считывает только данные удовлетворяющие условию.
  • Nested Loops (Соединение вложенным циклом) - пробегает по всем данным в цикле, работает всегда. Хорошо когда первая таблица маленькая, а вторая большая. При таком условии лучше Hash Match и Merge Join.
  • Hash Match (Соединение хешированием) - для соединения использует хеш, при поиске по набору ключей, работает при операторе равно "=". Может потреблять много оперативной памяти и если ее не хватит, то полезет на диск в tempdb.
  • Merge Join (Соединение слиянием) - самое быстрое слияние, требует сортировку в двух таблицах, а также наличие оператора равно "=". Хорошо на больших наборах данных.
  • Stream Aggregate (Агрегирование) - вычисляет агрегирующую функцию MAX, MIN, SUM, AVG, COUNT. по агрегируемой колонке обычно в группировках. Требует обязательную сортировку данных.
  • Обычно чем проще и меньше структура плана запроса, тем лучше.
  • Чем меньше данных обрабатывает SQL Server тем лучше, т.е. чем меньше чтений и записей тем система быстрее работает.
  • Общая мудрость гласит, что поиск (seek) - это хорошо для производительности, поскольку он представляет собой прямой доступ SQL Server к требуемым строкам данных, в то время как сканирование (scan) - это плохо, поскольку он предполагает последовательное чтение индекса для извлечения большого числа строк, приводя к более медленной обработке.
  • Соединения в плане запросов. Merge Join оптимально. Nested loops нормально для небольших данных. Hash match - присмотреться (возможно надо упорядочить сначала набор или индекс иметь на поля упорядочивания, по которым идет связь).
  • Чем более высокая нагрузка, тем сложнее серверу выбрать наиболее оптимальный план запросов.

План запроса читается справа налево и сверху вниз. В плане для источников данных приводятся имена таблиц, которые находятся в таблицах SQL сервера. Чтобы получить обратное сопоставление (как они называются в 1С дереве конфигуратора), нужно воспользоваться специальной обработкой или функцией " ПолучитьСтруктуруХраненияБазыДанных " - возвращает таблицу значений с описаниями структуры таблиц, индексов и полей базы данных в терминах модели базы данных 1С:Предприятия или используемой СУБД.

III) Ситуации

1. Некоторые базовые простые ситуации

В данных примерах мы посмотрим только на структуру без цифровых показателей значений выполнения запроса (CPU, длительность, количество чтений и записей и др.), т.к. примеры супер простые. Проверку и просмотр проводим на ненагруженном мощном сервере. Платформа 1С 8.3.16 и MS SQL сервер 13.

В качестве начального запроса возьмем таблицу регистра накопления "Товары Организаций", в ней около 10 миллионов записей, что вполне достаточно для наших задач. Регистр накопления "Расчеты с клиентами", где 3 миллиона записей и документ "Заказ клиента", записей порядка 700 тысяч.

Для примеров ниже в профайлере необходимо отключить фильтр длительности (Duration), т.к. запросы выполняются быстро и вы их пропустите. Остальные два фильтра по имени базы и вхождению ключа в текст оставляем.

Простой базовый запрос

Эталонный запрос для регистра накопления

ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ 10 Т.Период КАК Период, Т.Регистратор КАК Регистратор, Т.АналитикаУчетаНоменклатуры КАК АналитикаУчетаНоменклатуры, Т.Организация КАК Организация, Т.ВидЗапасов КАК ВидЗапасов, Т.НомерГТД КАК НомерГТД, Т.Количество КАК Количество, &КлючЗапроса как КлючЗапроса ИЗ РегистрНакопления.ТоварыОрганизаций КАК Т

Или для документа "Заказ клиента"

ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ 10 Т.Номер КАК Номер, Т.Дата КАК Дата, &КлючЗапроса КАК КлючЗапроса, Т.Организация КАК Организация, Т.Контрагент КАК Контрагент, Т.Склад КАК Склад ИЗ Документ.ЗаказКлиента КАК Т 

Резюме: Быстро и просто.

Добавим условие на равно

Добавим отбор по условию организации в запрос документа

ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ 10 Т.Номер КАК Номер, Т.Дата КАК Дата, &КлючЗапроса КАК КлючЗапроса, Т.Организация КАК Организация, Т.Контрагент КАК Контрагент, Т.Склад КАК Склад ИЗ Документ.ЗаказКлиента КАК Т ГДЕ Т.Организация = &Организация

Резюме: Быстро и просто

Добавим условие на не равно

Добавим отбор по условию организации в запрос документа

ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ 10 Т.Номер КАК Номер, Т.Дата КАК Дата, &КлючЗапроса КАК КлючЗапроса, Т.Организация КАК Организация, Т.Контрагент КАК Контрагент, Т.Склад КАК Склад ИЗ Документ.ЗаказКлиента КАК Т ГДЕ НЕ Т.Организация = &Организация

Резюме: Изменился оператор поиска данных на сканирование таблицы - это выглядит хуже.

Используем оператор "ИЛИ", "В"

Давайте выполним запрос с условием " ИЛИ ":

ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ 10 Т.Номер КАК Номер, Т.Дата КАК Дата, Т.Организация КАК Организация, Т.Контрагент КАК Контрагент, Т.Склад КАК Склад, &КлючЗапроса КАК КлючЗапроса ИЗ Документ.ЗаказКлиента КАК Т ГДЕ Т.Организация = &Организация ИЛИ Т.Организация = &Организация1

И вариант с оператором " В ":

ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ 10 Т.Номер КАК Номер, Т.Дата КАК Дата, Т.Организация КАК Организация, Т.Контрагент КАК Контрагент, Т.Склад КАК Склад, &КлючЗапроса КАК КлючЗапроса ИЗ Документ.ЗаказКлиента КАК Т ГДЕ Т.Организация В (&Организация, &Организация1)

План запроса система для них строит одинаковый. Т.к. у нас стоит маленькая выборка 10-25 записей, то пусть вас не смущает наличие оператора соединения Nested Loops (так работает оптимизатор и построитель плана запросов сервера), при больших значениях это будут соединение хешированием или слиянием.

Резюме: Получается с точки зрения SQL сервера практически нет разницы между оператором " ИЛИ " И " В " для набора констант.

2. Ситуации из статьи "Типовые ошибки разработчиков приводящие к проблемам".

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

Получаем данные через точку (пункт 10)

Давайте выведем через точку номер и дату документа для объекта "Заказ клиента"

ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ 10 Т.АналитикаУчетаПоПартнерам КАК АналитикаУчетаПоПартнерам, Т.ЗаказКлиента КАК ЗаказКлиента, Т.Валюта КАК Валюта, Т.СуммаОстаток КАК СуммаОстаток, Т.КОплатеОстаток КАК КОплатеОстаток, Т.ОплачиваетсяОстаток КАК ОплачиваетсяОстаток, Т.КОтгрузкеОстаток КАК КОтгрузкеОстаток, Т.ОтгружаетсяОстаток КАК ОтгружаетсяОстаток, &КлючЗапроса КАК КлючЗапроса, Т.ЗаказКлиента.Номер КАК ЗаказКлиентаНомер, Т.ЗаказКлиента.Дата КАК ЗаказКлиентаДата ИЗ РегистрНакопления.РасчетыСКлиентами.Остатки КАК Т

План запроса получился мягко говоря не читаемым. Таблица остатков соединилась по условию со всеми таблицами входящими в тип реквизита "Заказ клиента" (Объект учета). Это все присоединения к вертикальной цепочке. А теперь вспомним, что эта ужасная схема еще без RLS! Если появится разграничение доступа, то это будет выглядеть еще хуже.

Теперь добавим соединение с регистром "Реестр документов"

ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ 10 Т.АналитикаУчетаПоПартнерам КАК АналитикаУчетаПоПартнерам, Т.ЗаказКлиента КАК ЗаказКлиента, Т.Валюта КАК Валюта, Т.СуммаОстаток КАК СуммаОстаток, Т.КОплатеОстаток КАК КОплатеОстаток, Т.ОплачиваетсяОстаток КАК ОплачиваетсяОстаток, Т.КОтгрузкеОстаток КАК КОтгрузкеОстаток, Т.ОтгружаетсяОстаток КАК ОтгружаетсяОстаток, &КлючЗапроса КАК КлючЗапроса, ЕСТЬNULL(РД.ДатаДокументаИБ, ДАТАВРЕМЯ(1, 1, 1)) КАК ДатаДокументаИБ, ЕСТЬNULL(РД.НомерДокументаИБ, "") КАК НомерДокументаИБ ИЗ РегистрНакопления.РасчетыСКлиентами.Остатки КАК Т ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.РеестрДокументов КАК РД ПО (РД.Ссылка = Т.ЗаказКлиента) И (РД.ДополнительнаяЗапись = ЛОЖЬ)

План запроса теперь выглядит гораздо лучше. Тут мы видим соединение только с одной таблицей реестра документа. Однако, как мы видим не совпадение по индексу Index Seek (NonClustered) и дополнительный поиск ключа Key Lookup.

Т.к. мы с вами выбрали всего 10 записей, то у нас с вами на схеме плана запроса использовался оператор соединения таблиц Nested Loop (так посчитал оптимизатор). Если мы увеличим количество запрашиваемых данных до 10000, то план изменится следующим образом:

Вместо Nested Loop появился оператор соединения таблиц - Hash match, что ожидаемо, т.к. количество таблиц значительно увеличилось и теперь не выгодно использовать предыдущий оператор.

Приведем таблицу сравнения выполнения двух запросов, в которой показаны различия по стоимости выполнения.

Резюме: Выбор поля через точку для составного типа негативно сказывается на структуре плана запроса и времени выполнения данных.

Еще два варианта оптимизации через точку, когда не нужны данные всех типов (доп. к пункту 10)

Давайте предположим, что заказчик попросил вывести через точку только данные документов заказа клиента, тогда мы можем воспользоваться двумя вариантами " ВЫРАЗИТЬ " и " ССЫЛКА ".

Получим результаты для запроса с использованием оператора " ВЫРАЗИТЬ ":

ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ 10 Т.АналитикаУчетаПоПартнерам КАК АналитикаУчетаПоПартнерам, Т.ЗаказКлиента КАК ЗаказКлиента, Т.Валюта КАК Валюта, Т.СуммаОстаток КАК СуммаОстаток, Т.КОплатеОстаток КАК КОплатеОстаток, Т.ОплачиваетсяОстаток КАК ОплачиваетсяОстаток, Т.КОтгрузкеОстаток КАК КОтгрузкеОстаток, Т.ОтгружаетсяОстаток КАК ОтгружаетсяОстаток, &КлючЗапроса КАК КлючЗапроса, ВЫРАЗИТЬ(Т.ЗаказКлиента КАК Документ.ЗаказКлиента).Дата КАК ДатаДокументаИБ, ВЫРАЗИТЬ(Т.ЗаказКлиента КАК Документ.ЗаказКлиента).Номер КАК НомерДокументаИБ ИЗ РегистрНакопления.РасчетыСКлиентами.Остатки КАК Т

А теперь попробуем через оператор " ССЫЛКА ":

ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ 10 Т.АналитикаУчетаПоПартнерам КАК АналитикаУчетаПоПартнерам, Т.ЗаказКлиента КАК ЗаказКлиента, Т.Валюта КАК Валюта, Т.СуммаОстаток КАК СуммаОстаток, Т.КОплатеОстаток КАК КОплатеОстаток, Т.ОплачиваетсяОстаток КАК ОплачиваетсяОстаток, Т.КОтгрузкеОстаток КАК КОтгрузкеОстаток, Т.ОтгружаетсяОстаток КАК ОтгружаетсяОстаток, &КлючЗапроса КАК КлючЗапроса, Т.ЗаказКлиента.Дата КАК ДатаДокументаИБ, Т.ЗаказКлиента.Номер КАК НомерДокументаИБ ИЗ РегистрНакопления.РасчетыСКлиентами.Остатки(, ЗаказКлиента ССЫЛКА Документ.ЗаказКлиента) КАК Т

Получаем почти аналогичный план запроса первого варианта, отсутствует только фильтр, т.к. мы фильтр выполнили внутри временной таблицы.

Посмотрим как ведет себя система при получении через точку, когда реквизит обычный (не составного типа) (доп. к пункту 10)

Для этого возьмем запрос для документа "Заказ клиента" и выведем в список ИНН и КПП:

ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ 10 Т.Номер КАК Номер, Т.Дата КАК Дата, &КлючЗапроса КАК КлючЗапроса, Т.Организация КАК Организация, Т.Контрагент КАК Контрагент, Т.Контрагент.ИНН КАК ИНН, Т.Контрагент.КПП КАК КПП, Т.Склад КАК Склад ИЗ Документ.ЗаказКлиента КАК Т 

Как мы видим на картинке ниже, то добавилось соединение с таблицей контрагентов. И все ок.

Задание на перевозку и реквизит из табличной части (пункт 9)

План запроса для первого варианта будет выглядеть следующим образом:

ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ 10 Т.Ссылка КАК Ссылка, Т.Номер КАК Номер, Т.Дата КАК Дата, &КлючЗапроса КАК КлючЗапроса, Р.ПолучательОтправитель КАК ПолучательОтправитель ИЗ Документ.ЗаданиеНаПеревозку КАК Т ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаданиеНаПеревозку.Распоряжения КАК Р ПО (Р.Ссылка = Т.Ссылка) И (Р.НомерСтроки = 1)

Для второго запроса будет значительно проще, чем меньше связей - тем лучше:

ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ 10 Т.Ссылка КАК Ссылка, Т.Номер КАК Номер, Т.Дата КАК Дата, &КлючЗапроса КАК КлючЗапроса, Т.xyz_ПолучательОтправитель КАК ПолучательОтправитель ИЗ Документ.ЗаданиеНаПеревозку КАК Т

Сложный запрос в динамическом списке (пункт 11)

Давайте рассмотрим поведение сложного запроса с более простым

Сначала выполним сложный запрос:

ВЫБРАТЬ ПЕРВЫЕ 25 ДокументЗаказКлиента.Ссылка, ДокументЗаказКлиента.Номер, ДокументЗаказКлиента.Дата, ДокументЗаказКлиента.Организация, ДокументЗаказКлиента.Партнер, ДокументЗаказКлиента.Склад, ПризнакЗакрытияЗаявки.ПризнакЗакрытия, &КлючЗапроса ИЗ Документ.ЗаказКлиента КАК ДокументЗаказКлиента ЛЕВОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ ВыполнениеЗаявокОбороты.ДокументТочкиБП КАК ДокументТочкиБП, ВЫБОР КОГДА СУММА(ВыполнениеЗаявокОбороты.КоличествоРасход) = 0 ТОГДА 0 ИНАЧЕ ВЫБОР КОГДА СУММА(ВыполнениеЗаявокОбороты.КоличествоРасход) = СУММА(ВыполнениеЗаявокОбороты.КоличествоПриход) ТОГДА 2 ИНАЧЕ 1 КОНЕЦ КОНЕЦ КАК ПризнакЗакрытия ИЗ РегистрНакопления.ВыполнениеЗаявок.Обороты(, , , ДокументТочкиБП ССЫЛКА Документ.ЗаказКлиента) КАК ВыполнениеЗаявокОбороты СГРУППИРОВАТЬ ПО ВыполнениеЗаявокОбороты.ДокументТочкиБП) КАК ПризнакЗакрытияЗаявки ПО ДокументЗаказКлиента.Ссылка = ПризнакЗакрытияЗаявки.ДокументТочкиБП

Теперь выполним легкий оптимизированный запрос:

ВЫБРАТЬ ПЕРВЫЕ 25 ДокументЗаказКлиента.Ссылка, ДокументЗаказКлиента.Номер, ДокументЗаказКлиента.Дата, ДокументЗаказКлиента.Организация, ДокументЗаказКлиента.Партнер, ДокументЗаказКлиента.Склад, ЕСТЬNULL(ПризнакЗакрытияЗаявки.СостояниеВыполнения,0) КАК ПризнакЗакрытия, &КлючЗапроса ИЗ Документ.ЗаказКлиента КАК ДокументЗаказКлиента ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СостояниеВыполненияЗаявок КАК ПризнакЗакрытияЗаявки ПО ДокументЗаказКлиента.Ссылка = ПризнакЗакрытияЗаявки.ДокументТочкиБП

Теперь приведем числовые значения замеров:

Резюме: Оптимизированный запрос по показателям и на схеме выглядит значительно лучше, даже учитывая, что замеры производились на полностью ненагруженном сервере. Исчезло агрегирование, проблема отсутствия необходимых индексов (об этом нам сообщает оператор KeyLookUp в первой схеме).

Сложный запрос в динамическом списке с учетом RLS (доп. к пункту 11)

Теперь давайте откроем предприятие под пользователем с ограничениями RLS. Алгоритм действий аналогичный. Выполним оба запроса из пункта 11 и посмотрим схемы (приводить здесь тексты не будем, смотрите выше). Т.к. запрос получается очень сложным из-за соединений вызванных таблицами RLS, то воспользуемся возможностями Plan Explorer чтобы уменьшить картинку и просмотреть самые ресурсоемкие части.

Схема плана запроса и таблица для первого случая:

Схема и план запроса для второго оптимизированного случая:

Приведем табличку сравнения:

Резюме: Оптимизированный запрос выполняется быстрее и стоит меньше по затратам, но за счет влияния RLS это уже не так очевидно. Однако при выполнении на нагруженном сервере ситуация будет выражена более явно.

Сложный запрос и отсутствие индекса (доп. к пункту 11)

Давайте посмотрим как выглядит отсутствие индекса. Возьмем старый запрос и удалим индекс у поля регистра накопления "ДокументТочкиБП".

После этого обновим базу и выполним запрос и увидим значительное ухудшение производительности.

Система нам подсказывает, что не хватает индекса и предлагает его создать.

Таблица свойств выполнения запроса без индекса выглядит следующим образом:

Таблица запроса с наличием индекса для сравнения:

Резюме: Отсутствие индекса в соединении значительно снижает производительность запроса, при анализе плана можно увидеть какого и его добавить.

IV) Как найти, увидеть на боевой базе существующие проблемы для анализа

В предыдущих примерах мы анализировали известные ситуации. Теперь давайте перейдем к вопросу: Как найти или увидеть проблемные ситуации в существующей рабочей базе? Что для этого необходимо сделать?

Проанализировать всю конфигурацию - это довольно сложная задача. Давайте попробуем воспользоваться более простым решением, т.к. даже "оптимальные" варианты реализации тех или иных конструкций могут преподнести неожидаемое поведение в боевых условиях на рабочем сервере. Поэтому будем смотреть техническую информацию работы реальной базы или тестового стенда, будем искать и исправлять проблемы на "лету". В качестве инструмента помощника возьмем конфигурацию "Мониторинг производительности". Вы можете использовать блокнот, ЦКК, ЦУП, монитор активности студии SQL сервера или еще другие инструменты, но мы по результатам многолетней эксплуатации пришли к заключению что этот инструмент достаточно прост, удобен и гибок, а также бесплатен и является Open Source.

1) Настройка и загрузка технологического журнала

Процесс настройки и подключения конфигурации Мониторинг производительности отлично расписан в статье "5 простых шагов и 15 минут на разворачивание инструмента мониторинга проблем производительности базы 1С".

Нам потребуется настроить получение долгих запросов. Интервалы вы можете выставить последовательно: начать с ограничения в 60 секунд, а далее снизить показатель до 10 секунд (эти пороговые значения вам лучше определить из опыта на своем окружении и они могут отличаться от рекомендованных). Мы ставим задачу поиска больших и тяжелых запросов, которые следует оптимизировать в первую очередь. Ставить значения менее некоторого порогового числа нет смысла, т.к. будет очень много информации, которую в большинстве своем нельзя оптимизировать без изменения архитектуры (много однотипных запросов).

После некоторого времени наработки у нас с вами появится первая информация. И мы можем приступить к следующему шагу.

2) Настройка и описание инструмента

В предыдущем пункте вы должны были без особых проблем выполнить настройку технологического журнала и загрузку данных в базу. Теперь давайте рассмотрим как удобно настроить интерфейс и на что смотреть и что нажимать.

Первым делом давайте откроем замер и добавим необходимые информационные поля (колонки) в динамический список "События замера" как на рисунке ниже:

  • Usr - пользователь, тот пользователь, под которым проявилась ситуация;
  • SessionID - номер сеанса, вы можете отследить его действия в журнале регистрации в рамках интервала, чтобы к примеру, увидеть какой документ проводился и т.п.;
  • p:processName - имя базы данных, в какой базе проявилась ситуации;
  • Context - информация по стеку кода, где ситуация произошла. Позиция кода где смотреть возникшую проблему;
  • Sql - текст запроса который выполнялся указанное время;
  • Rows - количество строк полученных по запросу. Для динамических списков обычно не большое (25-50) и с ключевым словом в запросе TOP. Полезно чтобы оценить адекватность передаваемых пользователю данных, к примеру, если передается пользователю миллионы строк данных, то значит что-то не так;
  • RowsAffected - количество строк добавленных во временную таблицу;
  • SQL:Param - параметры запроса, могут входить в Sql. Удобно чтобы посмотреть, что передавалось.

Теперь давайте посмотрим, как выглядит у нас список:

В поле замер выбираем необходимый замер. Для нас "долгие запросы (не отчеты)".

В верхней части списка у нас должен появиться отбор по текущей дате - больше или равно началу сегодняшнего дня. Если у вас нет такого отбора установите его, т.к. это ограничит количество анализируемых данных и позволит списку довольно быстро работать.

В поле фильтра "длительность" устанавливаем ограничение в 60 секунд (или другое большее число - зависит от ситуации) и начинаем смотреть, что у нас набралось.

3) Смотрим ситуации и анализируем
А) Пример "поиск подобно по всем колонкам".

Открываем понравившуюся нам позицию и начинам анализировать. Ставим курсор на выбранную строку:

Из контекста видно, что проблемная ситуация находится в динамическом списке "Сделки с клиентами". Давайте посмотрим почему у нас появился этот запрос в топе, т.к. сам запрос на языке запросов 1С выглядит достаточно просто и в нем нет особых проблем.

Пример запроса динамического списка сделки с клиентами

ВЫБРАТЬ СправочникСделкиСКлиентами.Ссылка, СправочникСделкиСКлиентами.ПометкаУдаления, СправочникСделкиСКлиентами.Предопределенный, СправочникСделкиСКлиентами.Код, СправочникСделкиСКлиентами.Наименование, СправочникСделкиСКлиентами.ВероятностьУспешногоЗавершения, СправочникСделкиСКлиентами.ВидСделки, СправочникСделкиСКлиентами.ДатаНачала, СправочникСделкиСКлиентами.ДатаОкончания, СправочникСделкиСКлиентами.Закрыта, СправочникСделкиСКлиентами.Ответственный, СправочникСделкиСКлиентами.Партнер КАК Клиент, СправочникСделкиСКлиентами.СоглашениеСКлиентом, СправочникСделкиСКлиентами.ПотенциальнаяСуммаПродажи, СправочникСделкиСКлиентами.Комментарий, СправочникСделкиСКлиентами.ПричинаПроигрышаСделки, СправочникСделкиСКлиентами.Статус, СправочникСделкиСКлиентами.ПереведенаНаУправлениеВРучную, СправочникСделкиСКлиентами.Представление, СправочникСделкиСКлиентами.ОбособленныйУчетТоваровПоСделке ИЗ Справочник.СделкиСКлиентами КАК СправочникСделкиСКлиентами

Далее посмотрим свойства параметров и обратим внимание на наличие " % " и вхождения в запрос оператора языка запросов " ПОДОБНО (LIKE) ". Двойной клик на полях соответствующих колонок "Sql" и "SQL:Param".

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

Резюме: В данном случае смотреть план запросов нет необходимости, т.к. проблема и решение на поверхности. Не используйте общий поиск по всем колонкам и просто отключите его. В результате время выполнения запроса сократиться на порядок.

Б) Выбор необоснованного количества данных.

Давайте посмотрим следующий пример, который на рисунке ниже.

Как мы видим, то происходит создание временной таблицы с количеством строк "RowsAffected" с более чем 620 тысяч строк. Это действительно большое количество для данных. Давайте посмотрим где это произошло по колонке "Context".

Это выполняется внешняя обработка, а позиция находится в модуле объекта и именно выполнение запроса.

Резюме: Тут ситуация не однозначная, требуется передать описание возникновения проблемы программистам в отдел разработки, для дальнейшего анализа и исправления ситуации. Возможно в запросе не хватает дополнительных фильтров или отборов судя по количеству помечаемых данных.

Б) Сортировки по неиндексированным полям в динамическом списке

Давайте посмотрим, что у нас с самым популярным списком в базе ЕРП. Как вы догадались, то это список называется "Заказы клиентов". Самое обидное что в этом примере пользователь получил пустой список и ждал более 2х минут.

Контекст запроса: "ДинамическийСписок.ПолучитьДанные : Документ.ЗаказКлиента.Форма.ФормаСпискаДокументов.Реквизит.Список".

Первым делом поглядим на наличие подобно, его нет. Давайте посмотрим на еще один интересный оператор запросов " УПОРЯДОЧИТЬ ПО (ORDER BY) ". Для этого возьмем запрос из колонки "Sql" посмотрим на него внимательней.

Как мы видим с вами, то сортировка присутствует и в него добавлено поле "Партнер". Дополнительно на быстродействие влияет наличие сложной RLS по этому пользователю. Также мы видим наличие некоторого количества отборов, в которые входит и партнер (не приведено на снимках экрана).

Пользователь при запросе от сотрудника сопровождения (поддержка) объяснил, что сортировку поставил случайно (кликнул по этому полю).

Резюме: Нам требуется убрать сортировку из динамического списка по неподходящему полю или выполнить его индексирование. Также можно взять консоль запросов и под этим пользователем посмотреть как меняется поведение (это можете сделать самостоятельно).

Г) Сложный запрос и дальнейший анализ по схеме выше

Давайте возьмем следующую позицию для запроса:

Открываем контекст и смотрим:

Тут у нас похоже открыта домашняя страница рабочего стола предприятия. Не понятно, какой из списков вносит проблемы. Давайте скопируем запрос и преобразуем SQL имена таблиц в 1С.

Мы видим, что это " ЖурналДокументов.Взаимодействия ". В запросе нет поиска по подобно и сортировки по "плохим" полям. Давайте откроем эту форму списка и посмотрим на запрос:

Запрос формы списка взаимодействия

ВЫБРАТЬ ВЫБОР КОГДА ЖурналДокументовВзаимодействияПереопределяемый.Ссылка ССЫЛКА Документ.Встреча ТОГДА ВЫБОР КОГДА ЖурналДокументовВзаимодействияПереопределяемый.ПометкаУдаления ТОГДА 10 ИНАЧЕ 0 КОНЕЦ КОГДА ЖурналДокументовВзаимодействияПереопределяемый.Ссылка ССЫЛКА Документ.ЗапланированноеВзаимодействие ТОГДА ВЫБОР КОГДА ЖурналДокументовВзаимодействияПереопределяемый.ПометкаУдаления ТОГДА 11 ИНАЧЕ 1 КОНЕЦ КОГДА ЖурналДокументовВзаимодействияПереопределяемый.Ссылка ССЫЛКА Документ.ТелефонныйЗвонок ТОГДА ВЫБОР КОГДА ЖурналДокументовВзаимодействияПереопределяемый.ПометкаУдаления ТОГДА 12 ИНАЧЕ 2 КОНЕЦ КОГДА ЖурналДокументовВзаимодействияПереопределяемый.Ссылка ССЫЛКА Документ.ЭлектронноеПисьмоВходящее ТОГДА ВЫБОР КОГДА ЖурналДокументовВзаимодействияПереопределяемый.ПометкаУдаления ТОГДА 13 ИНАЧЕ 3 КОНЕЦ КОГДА ЖурналДокументовВзаимодействияПереопределяемый.Ссылка ССЫЛКА Документ.ЭлектронноеПисьмоИсходящее ТОГДА ВЫБОР КОГДА ЖурналДокументовВзаимодействияПереопределяемый.ПометкаУдаления ТОГДА 14 ИНАЧЕ ВЫБОР КОГДА ЖурналДокументовВзаимодействияПереопределяемый.СтатусИсходящегоПисьма = ЗНАЧЕНИЕ(Перечисление.СтатусыИсходящегоЭлектронногоПисьма.Черновик) ТОГДА 15 КОГДА ЖурналДокументовВзаимодействияПереопределяемый.СтатусИсходящегоПисьма = ЗНАЧЕНИЕ(Перечисление.СтатусыИсходящегоЭлектронногоПисьма.Исходящее) ТОГДА 16 ИНАЧЕ 4 КОНЕЦ КОНЕЦ КОГДА ЖурналДокументовВзаимодействияПереопределяемый.Ссылка ССЫЛКА Документ.СообщениеSMS ТОГДА ВЫБОР КОГДА ЖурналДокументовВзаимодействияПереопределяемый.ПометкаУдаления ТОГДА 22 ИНАЧЕ ВЫБОР КОГДА ЖурналДокументовВзаимодействияПереопределяемый.СтатусИсходящегоПисьма = ЗНАЧЕНИЕ(Перечисление.СостоянияДокументаСообщениеSMS.Черновик) ТОГДА 17 КОГДА ЖурналДокументовВзаимодействияПереопределяемый.СтатусИсходящегоПисьма = ЗНАЧЕНИЕ(Перечисление.СостоянияДокументаСообщениеSMS.Исходящее) ТОГДА 18 КОГДА ЖурналДокументовВзаимодействияПереопределяемый.СтатусИсходящегоПисьма = ЗНАЧЕНИЕ(Перечисление.СостоянияДокументаСообщениеSMS.Доставляется) ТОГДА 19 КОГДА ЖурналДокументовВзаимодействияПереопределяемый.СтатусИсходящегоПисьма = ЗНАЧЕНИЕ(Перечисление.СостоянияДокументаСообщениеSMS.ЧастичноДоставлено) ТОГДА 21 КОГДА ЖурналДокументовВзаимодействияПереопределяемый.СтатусИсходящегоПисьма = ЗНАЧЕНИЕ(Перечисление.СостоянияДокументаСообщениеSMS.НеДоставлено) ТОГДА 23 КОГДА ЖурналДокументовВзаимодействияПереопределяемый.СтатусИсходящегоПисьма = ЗНАЧЕНИЕ(Перечисление.СостоянияДокументаСообщениеSMS.Доставлено) ТОГДА 24 ИНАЧЕ 17 КОНЕЦ КОНЕЦ КОНЕЦ КАК НомерКартинки, ЖурналДокументовВзаимодействияПереопределяемый.Ссылка, ЖурналДокументовВзаимодействияПереопределяемый.Дата, ЖурналДокументовВзаимодействияПереопределяемый.ПометкаУдаления, ЖурналДокументовВзаимодействияПереопределяемый.Номер, ЖурналДокументовВзаимодействияПереопределяемый.Автор, ЖурналДокументовВзаимодействияПереопределяемый.Входящий, ЖурналДокументовВзаимодействияПереопределяемый.Тема, ЖурналДокументовВзаимодействияПереопределяемый.Ответственный КАК Ответственный, ЕСТЬNULL(ПредметыВзаимодействийПереопределяемый.Рассмотрено, Ложь) КАК Рассмотрено, ЕСТЬNULL(ПредметыВзаимодействийПереопределяемый.РассмотретьПосле, ДатаВремя(1,1,1)) КАК РассмотретьПосле, ЖурналДокументовВзаимодействияПереопределяемый.Участники, ЖурналДокументовВзаимодействияПереопределяемый.Тип, ЖурналДокументовВзаимодействияПереопределяемый.СтатусИсходящегоПисьма, ЖурналДокументовВзаимодействияПереопределяемый.ЕстьВложения, ЕСТЬNULL(ПредметыВзаимодействийПереопределяемый.Предмет, НЕОПРЕДЕЛЕНО) КАК Предмет, ТИПЗНАЧЕНИЯ(ПредметыВзаимодействийПереопределяемый.Предмет) КАК ТипПредмета, ЖурналДокументовВзаимодействияПереопределяемый.УчетнаяЗапись, ЕСТЬNULL(ПредметыВзаимодействийПереопределяемый.ПапкаЭлектронногоПисьма, ЗНАЧЕНИЕ(Справочник.ПапкиЭлектронныхПисем.ПустаяСсылка)) КАК Папка, ЖурналДокументовВзаимодействияПереопределяемый.ПолученоОтправлено КАК ПолученоОтправлено, ВЫРАЗИТЬ(ЖурналДокументовВзаимодействияПереопределяемый.Размер / 1024 КАК ЧИСЛО(10, 2)) КАК Размер, ЖурналДокументовВзаимодействияПереопределяемый.Важность, ВЫБОР КОГДА ЖурналДокументовВзаимодействияПереопределяемый.Важность = ЗНАЧЕНИЕ(Перечисление.ВариантыВажностиВзаимодействия.Высокая) ТОГДА 2 КОГДА ЖурналДокументовВзаимодействияПереопределяемый.Важность = ЗНАЧЕНИЕ(Перечисление.ВариантыВажностиВзаимодействия.Низкая) ТОГДА 0 ИНАЧЕ 1 КОНЕЦ КАК ВажностьНомерКартинки ИЗ ЖурналДокументов.Взаимодействия КАК ЖурналДокументовВзаимодействияПереопределяемый ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ПредметыПапкиВзаимодействий КАК ПредметыВзаимодействийПереопределяемый ПО ЖурналДокументовВзаимодействияПереопределяемый.Ссылка = ПредметыВзаимодействийПереопределяемый.Взаимодействие

Резюме: В списке нам не видно каких-то особых проблем, он достаточно простой. Дальнейшее расследование причин необходимо осуществлять с применением профайлера.

Дальнейшие рассмотрение практических примеров и ситуаций планирую рассмотреть на конференции или в следующей статье. Также хочу затронуть тему нагрузочного тестирования - моделирования нагрузки на рабочем стенде для выявления ситуаций и поведения под нагрузкой SQL сервера. Данную тему рассматривал на конференции QADays, думаю будет интересно.

V) РЕЗЮМЕ

  1. Оптимизатор плана запроса в MS SLQ сервер очень умный (если так можно сказать про программу) и он хорошо выполняет свою работу. Для того чтобы он правильно работал необходимо следить за актуальностью статистики, использовать другие процедуры обслуживания сервиса. Некоторые рекомендации от вендора смотрите тут: Регламентные операции на уровне СУБД для MS SQL Server
  2. Следите внимательно и не допускайте использования реквизитов в запросах через точку для составных типов.
  3. Всегда где это возможно используйте отборы - по дате, по периоду, по организации, контрагентам, и т.п.
  4. Избегайте где возможно использования в динамических списках общего поля поиска, отключайте, удаляйте, заклеивайте его.
  5. Обращайте внимание на наличие в схеме плана запроса оператора KeyLookUP - это говорит об отсутствии необходимых индексов. Однако, в типовых конфигурациях из-за наличия общих разделителей необходимо будет просматривать и анализировать каждый такой оператор, чтобы их отсеять. Добавление этих разделителей значительно ухудшило информативность схемы плана запросов и снизило возможности оптимизации, т.к. работа с этими реквизитами осуществляется платформой.
  6. Для очень сложных планов запросов используйте Plan Explorer и ищите "жирные" потоки данных, для того чтобы понять откуда идут проблемы. Если жирных потоков нет, то скорее всего требуется пересмотреть архитектуру решения.
  7. Проблема для динамических списков может иметь простое объяснение - прежде чем открывать профайлер посмотрите на наличие в тексте запроса "паразитных" вхождений: ПОДОБНО (LIKE) , УПОРЯДОЧИТЬ ПО (ORDER BY) , СГРУППИРОВАТЬ ПО (GROUP BY) - это могут быть признаки неоптимальной работы с элементом интерфейса.
  8. Обращайте внимание на "огромные" количества получаемых данных или помещаемых во временные таблицы - это может говорить о том что не хватает фильтров или про некорректные алгоритмы процедур.

1с как посмотреть план запроса

Хочу рассказать о разработке, предназначенной для облегчения написания запросов "без кривизны"
Поскольку наша команда (gilev.ru) специализируется на повышении производительности, то вопрос автоматизации рутинных операций в нашем деле стал достаточно быстро. Очевидно, что повторяющиеся простые действия не надо делать «руками».

Кто бы что на разных курсах не рассказывал «про чудесные секреты», а основным показателем является количество оптимизированных запросов. Просто и банально.

Другими словами, большая часть проблем производительности 1С лежит в неоптимальных запросах. Даже многие блокировки — лишь следствие избыточного сканирования данных неоптимальными запросами.

Поэтому основная задача оптимизации всегда будет в том числе в оптимизации наиболее используемых запросов.
Мы написали свою обработку. Не бог весть что, но работу облегчает.

Но как говориться лучше один раз увидеть https://www.youtube.com/watch?v=q9bKv5LwRdk , чем сто раз услышать.

Поэтому отдаем на Ваш суд нашу консоль запросов, которую можно скачать на главной странице http://www.gilev.ru/#ConsoleGilevRu , на текущий момент это версия http://www.gilev.ru/1c/cloud/GilevRu_Console_1_5_1.epf

Нужно настроить консоль согласно инструкции http://www.gilev.ru/console_setup
Результат анализа отображается на нашем сервере https://isinka.gilev.ru/QueryAnalyzerService/

Важно. Готовы делится бесплатно анализом запросов взамен на Ваш обратный отзыв и рекомендации, а конкретнее:
• Написать в почту slava@gilev.ru запрос с ссылкой на эту ветку и указать учетную запись в наших сервисах - 10 запросов бесплатно
• по каждому запросу и обнаруженной рекомендации дать обратную связь нам, насколько ясна рекомендация, помогла ли она - еще 3 запроса бесплатно по каждой обратной связи
• написать отзыв на своей странице и сообщить нам об этом - 3 месяца безлимита
• написать отзыв в своем блоге и сообщить нам об этом- 1 месяц безлимита
• написать отзыв в своей ленте в социальных сетях и сообщить нам об этом- 1 неделя безлимита

Я думаю что на экзамене 1С:Эксперт нашей обработкой Вам пользоваться не разрешат – слишком легко сдавать будет 
А вот на наших курсах http://www.gilev.ru/kurs/ можно все , в том числе убедиться насколько это мощный инструмент ускорения любой информационной системы.
Уверен, со временем наш подход, реализованный в этой обработке станет новым стандартом в области оптимизации. Спасибо что дочитали до конца! 

Согласовано с Волшебником

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *