Что быстрее: ОБЪЕДИНИТЬ или ОБЪЕДИНИТЬ ВСЕ во вложенном запросе с группировкой?

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

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

Постановка задачи и варианты реализации

Представим, что у нас есть два регистра сведений (например, Регистр1 и Регистр2) объемом по 300–400 Гб. Нам нужно получить уникальный список значений поля Измерение1 из обоих источников. Проанализируем два основных подхода.

Вариант 1: Прямое использование оператора ОБЪЕДИНИТЬ

В этом случае дедупликация (удаление повторяющихся записей) происходит на уровне самого оператора объединения:


ВЫБРАТЬ
    Рег1.Измерение1 КАК Измерение1
ИЗ
    РегистрСведений.Рег1 КАК Рег1

ОБЪЕДИНИТЬ

ВЫБРАТЬ
    Рег2.Измерение1
ИЗ
    РегистрСведений.Рег2 КАК Рег2

Вариант 2: ОБЪЕДИНИТЬ ВСЕ с внешней группировкой

Здесь мы сначала собираем все данные без исключения (включая дубликаты), а затем «сворачиваем» их с помощью группировки во внешнем запросе:


ВЫБРАТЬ
    Выборка.Измерение1 КАК Измерение1
ИЗ
    (ВЫБРАТЬ
        Рег1.Измерение1 КАК Измерение1
    ИЗ
        РегистрСведений.Рег1 КАК Рег1
    
    ОБЪЕДИНИТЬ ВСЕ
    
    ВЫБРАТЬ
        Рег2.Измерение1
    ИЗ
        РегистрСведений.Рег2 КАК Рег2) КАК Выборка

СГРУППИРОВАТЬ ПО
    Выборка.Измерение1

Сравнение производительности: практические замеры

Проанализируем результаты реальных тестов на больших объемах данных. Практика показывает ошеломляющую разницу. На тестовой базе при выборке около 2.8 млн записей результаты распределились следующим образом:

  1. Вариант с ОБЪЕДИНИТЬ: 19.160 сек.
  2. Вариант с ОБЪЕДИНИТЬ ВСЕ + СГРУППИРОВАТЬ ПО: 8.144 сек.

На промышленной базе с объемом данных более 62 миллионов записей разрыв становится еще более существенным: 516 секунд для первого варианта против 127 секунд для второго. Таким образом, второй вариант оказался быстрее более чем в 4 раза. Выясним причину такого поведения СУБД.

Почему ОБЪЕДИНИТЬ работает медленнее?

Разберем техническую реализацию этих операций на уровне SQL-сервера. Когда мы используем оператор ОБЪЕДИНИТЬ (который транслируется в SQL-команду UNION), СУБД обязана гарантировать отсутствие дубликатов в результирующем наборе.

Для этого оптимизатор чаще всего выбирает алгоритм Sort Distinct. Процесс выглядит так:

  1. СУБД считывает данные из первого источника.
  2. СУБД считывает данные из второго источника.
  3. Система объединяет их в один поток и выполняет полную сортировку всего объема данных.
  4. После сортировки дубликаты оказываются рядом, и СУБД легко их удаляет.

Основная проблема здесь заключается в сортировке. Если данные не помещаются в оперативную память (а при 400 Гб это неизбежно), СУБД начинает использовать TempDB на диске. Дисковые операции ввода-вывода крайне медленны, что и создает «бутылочное горлышко».

Преимущества ОБЪЕДИНИТЬ ВСЕ и группировки

Рассмотрим, что происходит во втором варианте. Оператор ОБЪЕДИНИТЬ ВСЕ (транслируется в UNION ALL) просто «склеивает» результаты запросов без какой-либо проверки. Это практически мгновенная операция для СУБД.

Когда же данные попадают в блок СГРУППИРОВАТЬ ПО (GROUP BY), у оптимизатора появляется выбор. Вместо тяжелой сортировки он может использовать алгоритм Hash Aggregate:

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

Использование временных таблиц

Существует мнение, что вместо вложенного запроса стоит использовать временные таблицы. Рассмотрим этот сценарий:


ВЫБРАТЬ
    Рег1.Измерение1 КАК Измерение1
ПОМЕСТИТЬ ВТ_Данные
ИЗ
    РегистрСведений.Рег1 КАК Рег1

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    Рег2.Измерение1
ИЗ
    РегистрСведений.Рег2 КАК Рег2;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_Данные.Измерение1 КАК Измерение1
ИЗ
    ВТ_Данные КАК ВТ_Данные
СГРУППИРОВАТЬ ПО
    ВТ_Данные.Измерение1

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

Рекомендации стандартов 1С

Официальные стандарты разработки (ИТС) также указывают на то, что использование оператора ОБЪЕДИНИТЬ (UNION) крайне не рекомендуется. Основные причины:

Итоги и выводы

Проанализировав ситуацию, мы приходим к однозначному выводу. Если перед вами стоит задача объединения данных из нескольких таблиц с удалением дубликатов, придерживайтесь следующих правил:

  1. Всегда отдавайте предпочтение ОБЪЕДИНИТЬ ВСЕ.
  2. Если нужны уникальные значения, используйте внешнюю группировку (СГРУППИРОВАТЬ ПО) или ключевое слово РАЗЛИЧНЫЕ над результатом ОБЪЕДИНИТЬ ВСЕ.
  3. Избегайте прямого оператора ОБЪЕДИНИТЬ на больших таблицах, чтобы не провоцировать СУБД на выполнение ресурсоемкой сортировки.
  4. При работе с объемами в сотни гигабайт старайтесь минимизировать количество записей во временные таблицы, используя вложенные запросы для «потоковой» обработки данных.

Эти простые правила позволят вашим запросам работать стабильно и быстро даже при значительном росте объема базы данных.

← На главную