Консоль запросов и замер

Знакомимся с консолью запросов — рабочим инструментом отладки, — учимся честно замерять время выполнения и понимаем, зачем нужны индексы и измерения регистра.

Консоль запросов — внешняя обработка, в которой можно писать текст запроса, подставлять параметры, тут же выполнять его и видеть результат в таблице, не трогая код конфигурации.

Пока запрос — три строки, его можно отлаживать «в уме». Но реальный запрос отчёта — это соединения пяти таблиц, вложенные пакеты, десяток параметров. Ковырять такое прямо в модуле, перезапуская всю обработку ради каждой правки, — мучение. Поэтому у каждого 1С-разработчика есть консоль запросов: отдельное окно, где запрос живёт сам по себе. Меняешь текст — жмёшь «Выполнить» — видишь таблицу результата. Это главный инструмент и для отладки логики, и для поиска тормозов.

Что такое консоль запросов и откуда её взять

Консоль — это внешняя обработка (файл .epf), которая открывается в любой типовой конфигурации через «Файл → Открыть». Внутри — поле для текста запроса, таблица параметров и кнопка выполнения. Самые известные: «Консоль запросов» с диска ИТС от фирмы «1С» и открытые консоли сообщества. Все они делают одно и то же — избавляют вас от перекомпиляции ради проверки одной строчки запроса.

По сути консоль внутри выполняет ровно тот код, который вы иначе писали бы руками: создаёт объект Запрос, кладёт в него текст, подставляет параметры и вызывает Выполнить().

// Что делает консоль «под капотом», когда вы жмёте «Выполнить»
Запрос = Новый Запрос;
Запрос.Текст = ТекстИзПоляВвода;                 // ваш запрос из окна консоли
Запрос.УстановитьПараметр("Дата", ЗначениеПараметраДата);  // из таблицы параметров
Результат = Запрос.Выполнить();
ТаблицаНаФорме = Результат.Выгрузить();           // и показывает её в таблице

Как честно замерить время

«Кажется, стало быстрее» — не аргумент. Время меряют числом. В 1С для этого есть функция ТекущаяУниверсальнаяДатаВМиллисекундах(): засекаем момент до выполнения и после, вычитаем — получаем длительность в миллисекундах.

Запрос = Новый Запрос(ТекстЗапроса);
Запрос.УстановитьПараметр("Склад", Склад);

Начало = ТекущаяУниверсальнаяДатаВМиллисекундах();
Результат = Запрос.Выполнить();
Длительность = ТекущаяУниверсальнаяДатаВМиллисекундах() - Начало;

Сообщить("Запрос выполнен за " + Длительность + " мс, строк: " + Результат.Количество());

Результат:

Запрос выполнен за 1840 мс, строк: 12760

Две честные привычки при замере. Первое: первый прогон всегда медленнее — СУБД ещё не держит данные в кэше, диск читается «вхолодную». Меряйте два-три раза и смотрите на стабильные цифры, а не на первый выброс. Второе: замеряйте на объёме. На двадцати строках любой запрос «быстрый»; сравнивать «до» и «после» имеет смысл там, где данных много.

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

СуммаМс = 0;
КоличествоПрогонов = 5;
Для Прогон = 1 По КоличествоПрогонов Цикл
    Начало = ТекущаяУниверсальнаяДатаВМиллисекундах();
    Запрос.Выполнить();
    СуммаМс = СуммаМс + (ТекущаяУниверсальнаяДатаВМиллисекундах() - Начало);
КонецЦикла;
Сообщить("Среднее время: " + (СуммаМс / КоличествоПрогонов) + " мс");

Идея плана запроса

Запрос 1С не выполняется буквально сверху вниз, как написан. Сервер СУБД сначала строит план выполнения — стратегию, как именно достать данные: какие таблицы читать, в каком порядке соединять, использовать индекс или сканировать таблицу целиком. Именно план определяет, будет запрос работать 20 миллисекунд или 20 секунд.

Ключевое различие в плане — как читается таблица:

  • Поиск по индексу (index seek) — СУБД сразу прыгает к нужным строкам, как вы находите слово в словаре по алфавиту. Быстро.
  • Сканирование таблицы (table scan) — СУБД читает все строки подряд и проверяет каждую. Как читать словарь от корки до корки в поисках одного слова. Медленно, и тем хуже, чем больше таблица.

Реальный план смотрят инструментами сервера СУБД (в PostgreSQL — EXPLAIN ANALYZE, в MS SQL — «Actual Execution Plan»), а поймать проблемные запросы помогает технологический журнал платформы с событием длительных запросов. Это уже уровень эксперта. Но идею — «есть подходящий индекс → seek → быстро; нет → scan → медленно» — держать в голове нужно с первого дня.

Почему измерения регистра — это индексы

Вот где теория плана становится практикой 1С. Когда вы создаёте регистр (накопления или сведений), его измерения — это не просто «поля»: платформа автоматически строит по ним индексы в базе данных. Поэтому отбор по измерению регистра почти всегда идёт по индексу и работает быстро, а отбор по обычному реквизиту — нет.

// Склад и Номенклатура — ИЗМЕРЕНИЯ регистра ТоварыНаСкладах → есть индекс → быстро
ВЫБРАТЬ Остатки.КоличествоОстаток
ИЗ РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад И Номенклатура = &Ном) КАК Остатки

Практический вывод при проектировании: поля, по которым вы часто отбираете, должны быть измерениями регистра, а не ресурсами или реквизитами. А порядок измерений тоже важен — отбор эффективнее всего работает по «левым», первым измерениям (это свойство составного индекса). Ошиблись с составом или порядком измерений — и никакая переписка текста запроса потом не спасёт: индекса, который нужен, просто нет.

Как это работает

Замер в 1С меряет полное время: платформа транслировала запрос в SQL, отправила серверу СУБД, тот построил план, выполнил его и вернул данные, платформа их приняла. Поэтому в цифру входит и сеть, и построение плана, и чтение с диска. Отсюда и «холодный» первый прогон: во второй раз данные уже в кэше СУБД, план построен — время падает. Консоль запросов удобна ровно тем, что весь этот цикл повторяется по одной кнопке, и вы за минуту проверяете пять вариантов запроса вместо пяти перезапусков обработки.

Частые ошибки

  • Делать вывод по одному «холодному» прогону. Первый запуск всегда медленный из-за кэша. Меряйте несколько раз и берите стабильное значение.
  • Мерить на тестовой базе из десятка записей. Разница между планом с индексом и без него видна только на объёме. Проверяйте близко к рабочим данным.
  • Использовать ТекущаяДата() для замера. Её разрешение — одна секунда, миллисекунды вы не поймаете. Нужна именно ТекущаяУниверсальнаяДатаВМиллисекундах().
  • Отбирать по реквизиту вместо измерения и ждать скорости. Индекс есть у измерений; по обычному реквизиту СУБД сканирует таблицу.
  • Ставить измерением поле «на всякий случай». Каждое измерение — это индекс, который замедляет запись в регистр. Измерения — только то, по чему реально идёт отбор и итоги.

Итоги

  • Консоль запросов — внешняя обработка для отладки: пишешь текст, подставляешь параметры, сразу видишь результат без перекомпиляции конфигурации.
  • Время меряют функцией ТекущаяУниверсальнаяДатаВМиллисекундах() «до» и «после»; первый прогон «холодный», меряйте несколько раз и на объёме.
  • СУБД выполняет запрос по плану: поиск по индексу (seek) — быстро, сканирование таблицы (scan) — медленно.
  • Измерения регистра автоматически индексируются, поэтому отбор по измерению идёт по индексу и работает быстро.
  • Часто используемые для отбора поля закладывайте измерениями ещё при проектировании — переписать текст запроса задним числом индекс не создаст.
Проверьте себя
1. Какой функцией в 1С корректно замерить время выполнения запроса с точностью до миллисекунд?
AТекущаяДата() — вычесть значение до и после
BТекущаяУниверсальнаяДатаВМиллисекундах() — вычесть значение до и после
CДата() — она возвращает время выполнения последнего запроса
DВремяЗапроса() — встроенная функция объекта Запрос
2. Почему отбор по измерению регистра обычно работает быстрее, чем по обычному реквизиту?
AИзмерения хранятся в оперативной памяти, а реквизиты — на диске
BПо измерениям регистра платформа автоматически строит индексы, и СУБД идёт поиском по индексу вместо сканирования всей таблицы
CРеквизиты в запросах вообще нельзя использовать в условиях отбора
DИзмерения всегда числовые, а числа СУБД сравнивает быстрее строк