При работе с большими базами данных, где размер таблиц исчисляется сотнями гигабайт, а количество записей — десятками миллионов, вопрос оптимизации запросов перестает быть теоретическим и становится критически важным для работоспособности системы. Рассмотрим классическую задачу: нам необходимо выбрать уникальные значения измерений из двух или более крупных регистров. В языке запросов 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 млн записей результаты распределились следующим образом:
ОБЪЕДИНИТЬ: 19.160 сек.ОБЪЕДИНИТЬ ВСЕ + СГРУППИРОВАТЬ ПО: 8.144 сек.На промышленной базе с объемом данных более 62 миллионов записей разрыв становится еще более существенным: 516 секунд для первого варианта против 127 секунд для второго. Таким образом, второй вариант оказался быстрее более чем в 4 раза. Выясним причину такого поведения СУБД.
Разберем техническую реализацию этих операций на уровне SQL-сервера. Когда мы используем оператор ОБЪЕДИНИТЬ (который транслируется в SQL-команду UNION), СУБД обязана гарантировать отсутствие дубликатов в результирующем наборе.
Для этого оптимизатор чаще всего выбирает алгоритм Sort Distinct. Процесс выглядит так:
Основная проблема здесь заключается в сортировке. Если данные не помещаются в оперативную память (а при 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) без лишней записи на диск.
Официальные стандарты разработки (ИТС) также указывают на то, что использование оператора ОБЪЕДИНИТЬ (UNION) крайне не рекомендуется. Основные причины:
Distinct Sort.ОБЪЕДИНИТЬ там, где он заранее знает, что дублей быть не может (в этом случае нужно использовать ОБЪЕДИНИТЬ ВСЕ).UNION сложнее анализировать и оптимизировать.Проанализировав ситуацию, мы приходим к однозначному выводу. Если перед вами стоит задача объединения данных из нескольких таблиц с удалением дубликатов, придерживайтесь следующих правил:
ОБЪЕДИНИТЬ ВСЕ.Эти простые правила позволят вашим запросам работать стабильно и быстро даже при значительном росте объема базы данных.