При разработке сложных отчетов, проведении документов или написании тяжелых обработок в «1С:Предприятии» разработчики регулярно сталкиваются с дилеммой: использовать вложенные запросы (подзапросы) или разбивать логику на пакетные запросы с созданием временных таблиц. На первый взгляд, оба подхода решают одну и ту же задачу, но с точки зрения производительности СУБД, потребления памяти и стабильности системы между ними лежит огромная пропасть. Давайте подробно разберем по шагам, как работают эти механизмы под капотом СУБД, проанализируем рекомендации фирмы «1С» и выясним, какой подход и в каких сценариях будет наиболее оптимальным.
Для начала обратимся к официальным рекомендациям и требованиям к разработке. В системе стандартов фирмы «1С» существует специализированное и очень жесткое правило — Стандарт № std655 («Ограничения на соединения с вложенными запросами и виртуальными таблицами»). Согласно этому стандарту:
ПОЛНОЕ СОЕДИНЕНИЕ, ЛЕВОЕ СОЕДИНЕНИЕ или ВНУТРЕННЕЕ СОЕДИНЕНИЕ допускается только физические таблицы метаданных (справочники, документы, регистры) или предварительно подготовленные временные таблицы.Рассмотрим пример типичного неоптимального запроса с вложенным подзапросом в соединении:
ВЫБРАТЬ
Продажи.Номенклатура,
Продажи.Количество,
Цены.Цена
ИЗ
РегистрНакопления.Продажи КАК Продажи
ЛЕВОЕ СОЕДИНЕНИЕ (
ВЫБРАТЬ
ЦеныНоменклатуры.Номенклатура,
ЦеныНоменклатуры.Цена
ИЗ
РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры
ГДЕ
ЦеныНоменклатуры.ВидЦены = &ВидЦены
) КАК Цены
ПО Продажи.Номенклатура = Цены.Номенклатура
С точки зрения стандарта 1С, данный запрос необходимо переписать с использованием временной таблицы. Выясним технические причины такого требования.
Основная проблема вложенных запросов кроется в механизме работы оптимизатора СУБД (например, MS SQL Server или PostgreSQL). Чтобы построить эффективный план выполнения запроса, СУБД должна решить, каким именно алгоритмом соединять данные. Существует три базовых физических оператора соединения:
Для выбора оптимального оператора СУБД использует механизм оценки кардинальности (Cardinality Estimation). СУБД пытается предугадать, сколько строк вернет каждый из источников данных:
В результате оптимизатор строит план практически вслепую. Если СУБД предположит, что вложенный запрос вернет 10 строк (и выберет быстрый Nested Loops), а на практике он вернет 1 000 000 строк, то запрос, который должен отрабатывать за доли секунды, «зависнет» на часы. Запросы со вложенными соединениями крайне нестабильны: они могут летать сразу после очистки кэша или обновления статистики СУБД, но резко деградировать при малейшем изменении объема данных в таблицах.
Существует распространенное среди начинающих разработчиков заблуждение, что временные таблицы в 1С создаются исключительно в оперативной памяти сервера приложений или СУБД. Это не так. Временные таблицы в платформе «1С:Предприятие» — это полноценные физические таблицы на стороне СУБД:
TempDB и получают уникальные имена вида #tt1....pg_temp.СУБД, разумеется, старается кэшировать страницы этих таблиц в оперативной памяти (в буферном пуле). Однако при нехватке оперативной памяти или при обработке значительных объемов данных временные таблицы активно сбрасываются на физические диски. Это означает, что неконтролируемое создание гигантских временных таблиц способно парализовать дисковую подсистему сервера СУБД.
Хотя временные таблицы защищают нас от ошибок оптимизатора СУБД, они не являются бесплатной «серебряной пулей». Использование временных таблиц несет в себе серьезные архитектурные накладные расходы:
SELECT. Когда мы пишем пакетный запрос с временной таблицей, на уровне СУБД это транслируется в целую серию технологических операций:
CREATE TABLE #tt1...).INSERT INTO #tt1... SELECT...).SELECT ... FROM ... JOIN #tt1).DROP TABLE #tt1).TempDB. Возникают специфические ожидания на системных защелках (например, задержки типа PAGELATCH_UP или PAGELATCH_SH на страницах распределения пространства PFS и SGAM). База данных начинает тормозить не из-за нехватки процессора или медленных дисков, а из-за внутренней конкуренции потоков за право создать временную таблицу в СУБД.Почему же тогда временные таблицы в большинстве случаев предпочтительнее? Главное и неоспоримое преимущество временных таблиц перед вложенными запросами — это возможность их индексации.
В языке запросов 1С для этого используется конструкция ПОМЕСТИТЬ ... ИНДЕКСИРОВАТЬ ПО .... Давайте перепишем наш первый пример правильно, используя пакетный запрос с индексированием временной таблицы:
ВЫБРАТЬ
ЦеныНоменклатуры.Номенклатура КАК Номенклатура,
ЦеныНоменклатуры.Цена КАК Цена
ПОМЕСТИТЬ Вт_ЦеныНоменклатуры
ИЗ
РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры
ГДЕ
ЦеныНоменклатуры.ВидЦены = &ВидЦены
ИНДЕКСИРОВАТЬ ПО
Номенклатура
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Продажи.Номенклатура,
Продажи.Количество,
Вт_ЦеныНоменклатуры.Цена
ИЗ
РегистрНакопления.Продажи КАК Продажи
ЛЕВОЕ СОЕДИНЕНИЕ Вт_ЦеныНоменклатуры КАК Вт_ЦеныНоменклатуры
ПО Продажи.Номенклатура = Вт_ЦеныНоменклатуры.Номенклатура
Что это дает на практике?
Table Scan). На больших выборках это приводит к катастрофическому падению скорости.Номенклатура) позволяет СУБД использовать мгновенный поиск по индексу (операция Index Seek). Время поиска нужной строки сокращается логарифмически.Стоит отметить, что индексирование также имеет свою цену: создание индекса замедляет процесс вставки данных во временную таблицу. Поэтому индексировать имеет смысл только те поля, по которым в последующих запросах пакета будет производиться соединение (JOIN) или отбор (ГДЕ), и только если объем временной таблицы превышает несколько десятков строк.
Давайте сведем все полученные знания в единую, понятную матрицу принятия решений при проектировании запросов в 1С:
Используйте вложенные запросы, если:
TempDB гарантированно превысят выигрыш от оптимизации плана запроса.JOIN) с другими таблицами (например, простой вложенный отбор в конструкции ГДЕ Поле В (ВЫБРАТЬ...)).Используйте пакетные запросы с временными таблицами, если:
JOIN) подзапроса с любой другой таблицей базы данных.Таким образом, золотое правило разработчика 1С звучит так: «Соединяем только физические или временные таблицы. Временные таблицы большого объема — индексируем. Вложенные запросы оставляем только для простейших и гарантированно маленьких выборок». Соблюдение этого правила убережет вашу систему от внезапных блокировок и критического падения производительности при росте базы данных.