При разработке запросов в системе «1С:Предприятие 8» программисты часто сталкиваются с дилеммой: использовать ли простую конструкцию ГДЕ Ссылка В (&Список) или же поместить данные во временную таблицу и выполнить ВНУТРЕННЕЕ СОЕДИНЕНИЕ. На первый взгляд, второй вариант кажется избыточным и сложным, особенно если данных немного. Однако производительность системы на больших объемах может существенно измениться. Разберем детально, как работают эти механизмы, проведем сравнение и выясним, где находится «точка невозврата» для каждого метода.
Рассмотрим стандартную ситуацию: у нас есть таблица значений тбзДанные с кодами товаров, и нам нужно выбрать ссылки на номенклатуру из справочника, соответствующие этим кодам. Проанализируем два варианта реализации этого запроса.
Вариант 1. Использование временной таблицы (ВТ)
В этом случае мы сначала передаем таблицу значений в запрос, помещаем её во временную таблицу и затем соединяем её с таблицей справочника.
// Текст запроса с временной таблицей
"ВЫБРАТЬ
| ТбзДанные.КодТовара КАК КодТовара
|ПОМЕСТИТЬ втДанные
|ИЗ
| &ТбзДанные КАК ТбзДанные
|;
|
|ВЫБРАТЬ
| СправочникНоменклатура.Ссылка КАК НоменклатураСсылка,
| втДанные.КодТовара КАК КодТовара
|ИЗ
| Справочник.Номенклатура КАК СправочникНоменклатура
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ втДанные КАК втДанные
| ПО СправочникНоменклатура.асИдентификаторГЛ = втДанные.КодТовара"
Вариант 2. Использование оператора «В»
Здесь мы напрямую передаем список (массив или таблицу значений) в условие отбора ГДЕ.
// Текст запроса с оператором В
"ВЫБРАТЬ
| СправочникНоменклатура.Ссылка КАК НоменклатураСсылка,
| СправочникНоменклатура.асИдентификаторГЛ КАК асИдентификаторГЛ
|ИЗ
| Справочник.Номенклатура КАК СправочникНоменклатура
|ГДЕ
| СправочникНоменклатура.асИдентификаторГЛ В (&ТбзДанные)"
Проанализируем результаты практических замеров. При небольшом количестве строк в исходной таблице (например, до 100 строк) второй вариант с оператором В работает быстрее. Это объясняется тем, что системе не нужно тратить время на создание временной таблицы в базе данных (СУБД), выделение под неё памяти и выполнение транзакционных операций по записи данных.
Однако ситуация кардинально меняется при увеличении объема данных. Экспериментальным путем выяснено, что порог эффективности находится в районе 130 элементов:
В может быть в 3 раза быстрее, чем вариант с временной таблицей.С ростом количества элементов в списке (до тысяч и десятков тысяч) разрыв в скорости становится колоссальным. Использование В (&Параметр) на огромных списках может привести к крайне медленной работе или даже к падению запроса с ошибкой СУБД.
Чтобы понять причины такой разницы, разберем, как платформа 1С транслирует эти запросы в язык SQL, понятный для сервера базы данных (например, MS SQL Server или PostgreSQL).
1. Механизм оператора «В» (IN)
Когда мы пишем ГДЕ Поле В (&Параметр), платформа 1С превращает это в SQL-конструкцию WHERE Field IN (@P1, @P2, ..., @PN). Для СУБД это выглядит как перечисление конкретных значений. Оптимизатор запросов СУБД в этом случае чаще всего использует алгоритм Nested Loops (Вложенные циклы). Он берет каждое значение из вашего списка и ищет его в индексе справочника. Если список маленький — это быстро. Если список большой — количество «циклов» поиска становится огромным.
2. Механизм Временных Таблиц
При использовании ПОМЕСТИТЬ 1С создает реальную временную таблицу в системной базе данных СУБД (например, tempdb). После заполнения этой таблицы данными выполняется полноценное соединение таблиц. В этом случае оптимизатор СУБД может применить более мощные алгоритмы, такие как Hash Join или Merge Join. Эти методы гораздо эффективнее обрабатывают большие наборы данных, так как они работают с массивами данных целиком, а не перебирают их по одному элементу.
Важный аспект, который часто упускают разработчики — это повторное использование планов выполнения запросов. СУБД кэширует план выполнения для каждого уникального текста запроса.
В случае с оператором В, если сегодня вы передали 5 элементов, а завтра 10, текст SQL-запроса изменится (количество параметров @P будет разным). СУБД сочтет это за новый запрос и будет заново строить план выполнения, что расходует ресурсы сервера. Временные таблицы избавляют от этой проблемы: текст основного запроса к базе остается неизменным независимо от того, сколько строк вы положили во временную таблицу.
Не стоит забывать и о жестких ограничениях СУБД. Например, MS SQL Server поддерживает не более 2100 параметров в одном запросе. Если вы попытаетесь передать в оператор В массив, содержащий 3000 элементов, запрос завершится с ошибкой. Временные таблицы не имеют такого ограничения, так как данные передаются не через параметры текста запроса, а через механизм загрузки данных в таблицу.
Выясним еще один критический момент: для достижения максимальной скорости при использовании временных таблиц их необходимо индексировать. Рассмотрим, как правильно дополнить запрос из первого примера:
"ВЫБРАТЬ
| ТбзДанные.КодТовара КАК КодТовара
|ПОМЕСТИТЬ втДанные
|ИЗ
| &ТбзДанные КАК ТбзДанные
|;
|
|////////////////////////////////////////////////////////////////
|// Создаем индекс по полю связи
|ИНДЕКСИРОВАТЬ ПО
| КодТовара
|;
|
|////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| СправочникНоменклатура.Ссылка КАК НоменклатураСсылка,
| втДанные.КодТовара КАК КодТовара
|ИЗ
| Справочник.Номенклатура КАК СправочникНоменклатура
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ втДанные КАК втДанные
| ПО СправочникНоменклатура.асИдентификаторГЛ = втДанные.КодТовара"
Без индексации временной таблицы СУБД придется выполнять полное сканирование этой таблицы при каждом соединении, что может нивелировать все преимущества её использования на средних объемах данных.
В ходе обсуждения возник вопрос: можно ли передавать Таблицу Значений напрямую в параметр условия В (&Параметр)? Современные версии платформы 1С позволяют это делать. Платформа автоматически берет значения из первой колонки таблицы значений. Однако, проанализировав правила хорошего тона в разработке 1С, мы рекомендуем явно выгружать колонку в массив перед передачей в запрос:
// Правильный подход
МассивКодов = ТбзДанные.ВыгрузитьКолонку("КодТовара");
Запрос.УстановитьПараметр("СписокКодов", МассивКодов);
Это делает код более предсказуемым и защищает от ошибок, если структура таблицы значений изменится и нужная колонка перестанет быть первой.
Подведем итог нашему анализу и определим правила выбора архитектуры запроса:
JOIN) или отбор (WHERE).Соблюдение этих правил позволит вашим решениям работать стабильно быстро как на пустых базах, так и на высоконагруженных системах с миллионами записей.