При разработке на платформе 1С:Предприятие 7.7 программисты часто сталкиваются с вопросом оптимизации так называемых «черных» (штатных) запросов. В частности, как правильно обращаться к реквизитам в функции Условие(), чтобы запрос выполнялся максимально быстро? Давайте разберем эту ситуацию подробно, проанализируем результаты трассировки и посмотрим на альтернативные способы работы с данными.
Рассмотрим два варианта написания одного и того же логического блока в тексте запроса:
Вариант 1 (с предварительным объявлением переменных):
|Груз = Регистр.Остатки.Груз;
|НомерМеста = Регистр.Остатки.Груз.НомерМеста;
...
|Условие(НомерМеста=0);
Вариант 2 (с прямым обращением в условии):
|Груз = Регистр.Остатки.Груз;
...
|Условие(глЗапрос.Груз.НомерМеста=0);
На первый взгляд, разница невелика, но с точки зрения внутреннего механизма выполнения запросов 1С, она колоссальна. Разберем по шагам, что происходит «под капотом».
Для понимания того, как платформа обрабатывает эти конструкции, проанализируем результаты трассировки на DBF-версии базы данных. В тестировании участвовали три варианта написания запроса:
Тест 1 (оптимальный):
|Номенклатура = Справочник.Номенклатура.ТекущийЭлемент;
|Продажа = Справочник.Номенклатура.Сумма;
|Коэффициент = Справочник.Номенклатура.БазоваяЕдиница.Коэффициент;
|Условие(Коэффициент<>1);
Тест 2 (обращение через переменную запроса):
|Номенклатура = Справочник.Номенклатура.ТекущийЭлемент;
|Единица = Справочник.Номенклатура.БазоваяЕдиница;
|Условие(глЗапрос.Единица.Коэффициент<>1);
Тест 3 (длинный путь в условии):
|Номенклатура = Справочник.Номенклатура.ТекущийЭлемент;
|Условие(глЗапрос.Номенклатура.БазоваяЕдиница.Коэффициент<>1);
Что показала трассировка?
Номенклатура. Для каждой записи выполняется поиск по ключу в таблице Единицы. Проверка условия происходит в оперативной памяти, и, если оно истинно, запись попадает в рабочую таблицу. Объекты для чтения таблиц создаются один раз в начале цикла и освобождаются в конце.Единицы. Это крайне медленный процесс, так как он включает тупой перебор в оперативной памяти схемы базы данных (из файла 1Cv7.DD).Номенклатура читаются по два раза, каждый раз с созданием объектов манипуляции.Вывод: Каждая «лишняя точка» в функции Условие() при обращении через Запрос. многократно замедляет выполнение из-за постоянного создания/уничтожения COM-объектов работы с данными и обращения к словарю метаданных.
Если вы думаете, что в SQL-версии 1С 7.7 ситуация кардинально отличается, то это не совсем так. Движок платформы часто скачивает огромные объемы данных во временные DBF-файлы в папку Temp и выполняет группировки и фильтрации уже там. Поэтому Вариант 1 предпочтительнее и для SQL.
Кроме того, есть важный нюанс, связанный с индексами временных таблиц. При выполнении запроса 1С собирает все переменные из раздела Группировка (и некоторые другие необходимые переменные) и создает один длинный сборный индекс. Если этот индекс превышает допустимую длину, возникает ошибка: "Длина индекса превышает максимальную длину и не может быть уменьшена".
Объявление локальных переменных (как в Варианте 1) позволяет не только ускорить проверку условий, но и часто помогает оптимизировать длину этого внутреннего индексного выражения. Переменные запроса, которые не участвуют в группировках, но нужны для вывода результатов (например, код, артикул), лучше получать именно в теле запроса — это быстрее, чем потом в цикле при выводе отчета дергать базу данных через Запрос.Номенклатура.Артикул.
Несмотря на низкую производительность, конструкция Условие(Запрос.Реквизит...) иногда является единственным выходом. Это происходит, когда реквизит имеет составной тип данных (справочник неопределенного вида).
Если БазоваяЕдиница может содержать элементы из разных справочников (например, Справочник.Единицы и Справочник.ДопЕдиницы), то прямое обращение:
|Коэффициент = Справочник.Номенклатура.БазоваяЕдиница.Коэффициент;
вызовет ошибку при трансляции запроса, так как 1С не знает, в какой таблице искать поле Коэффициент. В этом случае приходится использовать конструкцию из Варианта 2.
Для существенного повышения производительности в DBF-базах опытные разработчики используют внешнюю компоненту 1SQLite. Она позволяет выполнять полноценные SQL-запросы к таблицам напрямую в памяти (или к физическим файлам).
Посмотрим на пример использования 1SQLite для подготовки запроса:
база = СоздатьОбъект("SQLiteBase");
// Открываем базу в памяти (обычно делается один раз при открытии обработки)
база.Открыть(":memory:");
рс = база.НовыйЗапрос();
// Подготовка параметризированного запроса
ТекстЗапроса = "select parentext [Товар :Справочник.Номенклатура] from Справочник_Единицы where Штрихкод=@ШК";
рс.Подготовить(ТекстЗапроса);
Табличка = стДок.Document_Table;
Для Сч=1 По Табличка.Количество() Цикл
лкСтрока = Табличка.Получить(Сч);
// Передаем параметр
рс.УстановитьПараметр("@ШК", лкСтрока.Barcode);
тз = рс.Выполнить();
// Обработка результата
КонецЦикла;
Использование подобных инструментов позволяет обойти все ограничения «черных» запросов, использовать настоящие JOIN'ы и сложные условия фильтрации без деградации производительности.
При написании штатных запросов в 1С 7.7 придерживайтесь следующих правил:
Запрос. (или имени переменной запроса) внутри функции Условие().