В практике разработки на платформе 1С программисты часто сталкиваются с парадоксальной ситуацией: запрос, который должен возвращать десятки тысяч строк, начинает работать значительно быстрее, если в него добавить оператор ПЕРВЫЕ с заведомо огромным лимитом (например, один миллион). На первый взгляд это кажется нелогичным — ведь объем данных не меняется. В этой статье мы подробно разберем, почему это происходит, как СУБД оптимизирует такие запросы и какие опасности таит в себе этот метод "ускорения".
Рассмотрим ситуацию, описанную в техническом кейсе. Исходный запрос без ограничений выдает результат в 50 000 строк за 1.5 секунды. Тот же самый запрос, но с добавлением конструкции ВЫБРАТЬ ПЕРВЫЕ 1000000, отрабатывает за 0.7 секунды. Разберемся по шагам, что происходит "под капотом" системы.
// Исходный запрос
ВЫБРАТЬ
ВложенныйЗапрос.Период КАК Период,
ВложенныйЗапрос.Регистратор КАК Регистратор,
ВложенныйЗапрос.Организация КАК Организация
ИЗ
(ВЫБРАТЬ ... ) КАК ВложенныйЗапрос
// Оптимизированный (как кажется) запрос
ВЫБРАТЬ ПЕРВЫЕ 1000000
ВложенныйЗапрос.Период КАК Период,
ВложенныйЗапрос.Регистратор КАК Регистратор,
ВложенныйЗапрос.Организация КАК Организация
ИЗ
(ВЫБРАТЬ ... ) КАК ВложенныйЗапрос
Основная причина такого поведения кроется в работе оптимизатора запросов СУБД (чаще всего MS SQL Server). Когда СУБД видит оператор TOP (в 1С это ПЕРВЫЕ), она переключает стратегию построения плана выполнения. Этот механизм называется Row Goal.
Проанализируем две разные цели оптимизатора:
Hash Join или Merge Join. Они эффективны на больших объемах, но требуют предварительной подготовки — построения хеш-таблиц в памяти или сортировки данных.ПЕРВЫЕ оптимизатор предполагает, что пользователю нужно как можно скорее увидеть начало выборки. Он меняет план в пользу алгоритмов, которые начинают выдавать результат немедленно, например, Nested Loops (вложенные циклы) совместно с Index Seek (поиск по индексу).Если для получения первых 50 000 строк из миллиона выбранный план с вложенными циклами оказывается удачным (например, данные физически расположены в нужном порядке в индексе), запрос отрабатывает в разы быстрее, чем при "тяжеловесном" хешировании.
Выясним, как именно выбор алгоритма соединения влияет на скорость. Рассмотрим пример со вложенными запросами и соединениями. В сложных запросах 1С часто используются виртуальные таблицы или временные таблицы.
При использовании ПЕРВЫЕ СУБД может решить, что выгоднее использовать Nested Loops. Этот алгоритм берет одну строку из ведущей таблицы и ищет совпадение в ведомой. Если ведомая таблица хорошо проиндексирована, поиск происходит мгновенно. В то время как Hash Join заставил бы систему прочитать обе таблицы целиком, создать хеш-таблицу в TempDB и только потом выдать результат. Именно на исключении стадии "подготовки" данных и выигрываются те самые доли секунд.
Еще один важный технический аспект — это резервирование памяти. Перед выполнением запроса SQL Server оценивает, сколько строк будет получено, и запрашивает у ОС соответствующий объем памяти (Memory Grant).
ПЕРВЫЕ 1 000 000, СУБД готовится к обработке большого объема и резервирует достаточно памяти для сортировок и группировок.tempdb.RESOURCE_SEMAPHORE), и время выполнения резко возрастет.Проанализируем результаты эксперимента, которые получил автор темы при увеличении лимита:
Почему при лимите в 100 миллионов время выросло катастрофически? Это произошло из-за того, что оптимизатор "поверил" вашему числу. Он построил план Nested Loops, рассчитывая, что это будет эффективно. Но для объема в 100 миллионов строк вложенные циклы — это худшее решение из возможных. СУБД начала выполнять миллиарды мелких операций поиска вместо одного быстрого сканирования таблицы. В итоге запрос без лимита оказался быстрее, так как СУБД изначально выбрала более стабильный план Hash Join.
Рассмотрим рекомендации по применению данного метода. Использование ПЕРВЫЕ как "костыля" для ускорения — это плохая практика по нескольким причинам:
Nested Loops быстр. Завтра данных станет больше, индекс фрагментируется, и этот же запрос начнет тормозить или вызывать блокировки.ПЕРВЫЕ без УПОРЯДОЧИТЬ ПО может вернуть разный набор данных в разное время, хотя СУБД и будет пытаться использовать физический порядок индекса.Вместо использования искусственных ограничений, разберем стандартные шаги оптимизации запросов в 1С:
МенеджерВременныхТаблиц, проиндексируйте её по полям связи, а затем выполняйте соединение.ГДЕ и в связях ПО, входят в состав индексов.Итог: Эффект ускорения при добавлении ПЕРВЫЕ — это побочное действие механизма Row Goal. Его можно использовать для разовых отчетов в консоли запросов, но крайне не рекомендуется внедрять в тиражные решения. Правильная оптимизация всегда заключается в работе со структурой запроса, индексами и актуальностью статистики СУБД.