Что производительнее в запросах 1С: использование соединений или условий «В (ВЫБРАТЬ...)»?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

Существует два основных подхода к решению этой задачи: использование ВНУТРЕННЕГО СОЕДИНЕНИЯ или использование оператора ГДЕ Ссылка В (ВЫБРАТЬ Ссылка ИЗ ВТ1). Разберем детально, какой из этих методов эффективнее, как на них реагирует оптимизатор СУБД и какие нюансы следует учитывать при работе с большими объемами данных.

Вариант 1: Использование ВНУТРЕННЕГО СОЕДИНЕНИЯ

Этот метод считается стандартным и наиболее прозрачным для разработчика. Мы явно указываем СУБД, как связать две таблицы. Рассмотрим пример кода:


ВЫБРАТЬ
    ВТ2.Ссылка,
    ВТ2.Наименование,
    ВТ2.Сумма
ИЗ
    ВТ2 КАК ВТ2
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ1 КАК ВТ1
        ПО ВТ2.Ссылка = ВТ1.Ссылка

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

Важный нюанс: семантика дублей. Проанализируем ситуацию, когда в таблице ВТ1 одна и та же ссылка встречается несколько раз. В случае ВНУТРЕННЕГО СОЕДИНЕНИЯ строки из ВТ2 будут размножены в итоговом результате ровно столько раз, сколько повторений этой ссылки найдено в ВТ1. Если ваша задача — просто отфильтровать данные, это может привести к логической ошибке (удвоению сумм, повтору строк). Чтобы избежать этого, перед соединением таблицу ВТ1 необходимо сгруппировать или выбрать только различные значения.

Вариант 2: Использование условия «ГДЕ Ссылка В (...)»

Этот метод часто называют полусоединением (Semi-join). С точки зрения языка запросов 1С это выглядит следующим образом:


ВЫБРАТЬ
    ВТ2.Ссылка,
    ВТ2.Наименование,
    ВТ2.Сумма
ИЗ
    ВТ2 КАК ВТ2
ГДЕ
    ВТ2.Ссылка В (ВЫБРАТЬ ВТ1.Ссылка ИЗ ВТ1)

Рассмотрим, чем этот вариант отличается от предыдущего. Главное преимущество оператора В заключается в том, что он гарантирует уникальность строк результата. Даже если в ВТ1 миллион дублирующихся ссылок, каждая строка из ВТ2 попадет в выборку только один раз. Это избавляет от необходимости предварительной группировки таблицы-фильтра.

Существует распространенное заблуждение, что СУБД сначала выбирает все записи из основной таблицы, а потом фильтрует их. На самом деле современные оптимизаторы (MS SQL Server, PostgreSQL) используют технологию Predicate Pushdown (проталкивание предиката). Они способны преобразовать условие В во внутренний план выполнения, который будет работать так же эффективно, как и соединение.

Сравнение производительности и работа оптимизатора

Выясним причину, по которой один запрос может работать быстрее другого. В современных СУБД выбор физического метода выполнения (плана запроса) зависит не столько от того, написали вы JOIN или WHERE IN, сколько от накопленной статистики и наличия индексов.

Рассмотрим основные алгоритмы, которые применяет СУБД:

  1. Nested Loops (Вложенные циклы): Этот метод идеален, если ВТ1 (фильтр) очень маленькая, а ВТ2 большая и проиндексирована. СУБД берет каждую запись из ВТ1 и мгновенно находит соответствие в ВТ2.
  2. Hash Match (Хеш-соединение): Используется, когда обе таблицы достаточно велики и не имеют индексов. СУБД строит временную хеш-таблицу в оперативной памяти. Это требует ресурсов, но работает быстрее прямого перебора.
  3. Merge Join (Соединение слиянием): Самый быстрый вариант для огромных объемов данных, но требует предварительной сортировки обеих таблиц по полям связи. Именно наличие индексов в 1С обеспечивает эту сортировку "бесплатно".

Посмотрим на роль индексов во временных таблицах. В 1С временные таблицы физически создаются в базе данных (например, в tempdb для MS SQL). Использование конструкции ИНДЕКСИРОВАТЬ ПО для полей связи критически важно. Без индекса при объемах свыше 10-20 тысяч записей производительность упадет в геометрической прогрессии.

Проблема статистики во временных таблицах

Проанализируем ситуацию, когда даже правильный запрос работает медленно. Одной из специфических проблем 1С является то, что SQL-сервер не всегда успевает обновить статистику по только что созданным временным таблицам. Оптимизатор может ошибочно решить, что в ВТ1 всего одна строка (хотя их там тысячи), и выбрать неэффективный план Nested Loops вместо Hash Match.

В новых версиях платформы 1С и современных СУБД эта проблема частично нивелируется механизмом отложенной компиляции запроса, но общая рекомендация остается прежней: старайтесь индексировать поля, по которым происходит связь или фильтрация.

Практические рекомендации по выбору метода

Подведем итог нашему анализу и определим, что и в каких случаях использовать:

  1. Если вы на 100% уверены, что в таблице-фильтре (ВТ1) записи уникальны, используйте ВНУТРЕННЕЕ СОЕДИНЕНИЕ. Это считается «золотым стандартом» разработки в 1С, так как план такого запроса наиболее предсказуем для оптимизатора.
  2. Если в таблице-фильтре могут быть дубли, и вы не хотите тратить время на их группировку, используйте условие В (ВЫБРАТЬ...). Это безопаснее с точки зрения бизнес-логики.
  3. Всегда индексируйте поля связи во временных таблицах, если количество записей в них превышает 100–200 строк. Для очень маленьких таблиц (до 50 строк) индекс может быть избыточен, так как затраты на его создание превысят выгоду от использования.
  4. Для PostgreSQL разница между этими конструкциями практически стерта, так как его планировщик агрессивно преобразует подзапросы в соединения. В старых версиях MS SQL Server (до 2012 года) явное соединение зачастую работало стабильнее.

Рассмотрим пример правильной организации запроса с индексацией:


// Создаем первую временную таблицу с индексом
МенеджерВТ.Текст = 
"ВЫБРАТЬ РАЗЛИЧНЫЕ Ссылка ИЗ Документ.РеализацияТоваровУслуг ГДЕ ... ПОМЕСТИТЬ ВТ1 ИНДЕКСИРОВАТЬ ПО Ссылка";

// Выполняем основной запрос с соединением
МенеджерВТ.Текст = 
"ВЫБРАТЬ ВТ2.* ИЗ ВТ2 КАК ВТ2 ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ1 КАК ВТ1 ПО ВТ2.Ссылка = ВТ1.Ссылка";

Таким образом, выбор между соединением и условием — это баланс между чистотой кода, контролем над дублями и помощью оптимизатору СУБД. В большинстве случаев в 1С предпочтительнее явное соединение по проиндексированным полям.

← На главную