Как (и почему) TOP влияет на план выполнения?

35

Для довольно сложного запроса, который я пытаюсь оптимизировать, я заметил, что удаление TOP nпредложения меняет план выполнения. Я бы предположил, что когда запрос включает TOP nв себя механизм базы данных, он будет запускать запрос, игнорируя TOPпредложение, а затем в конце просто сократит этот результат до n запрошенных строк. Графический план выполнения, кажется, указывает на то, что это так - TOPэто «последний» шаг. Но, похоже, что-то еще происходит.

Мой вопрос: как (и почему) предложение TOP n влияет на план выполнения запроса?

Вот упрощенная версия того, что происходит в моем случае:

Запрос сопоставляет строки из двух таблиц A и B.

Без этого TOPпредложения оптимизатор оценивает, что будет 19k строк из таблицы A и 46k строк из таблицы B. Фактическое число возвращаемых строк составляет 16k для A и 13k для B. Совпадение хеша используется для объединения этих двух наборов результатов для всего 69 строк (затем применяется сортировка). Этот запрос происходит очень быстро.

Когда я добавляю, TOP 1001оптимизатор не использует хеш-соответствие; вместо этого он сначала сортирует результаты из таблицы A (та же самая оценка / факт 19k / 16k) и выполняет вложенный цикл для таблицы B. Предполагаемое число строк для таблицы B теперь равно 1, и странно то, что TOP nнепосредственно влияет на оценочное количество казней (поиск по индексу) против B - оно всегда равно 2n + 1 , или в моем случае 2003. Эта оценка изменяется соответственно, если я изменяюсь TOP n. Конечно, поскольку это вложенное соединение, фактическое количество выполнений составляет 16 КБ (количество строк в таблице А), и это замедляет запрос.

Реальный сценарий немного сложнее, но он отражает основную идею / поведение. Обе таблицы ищутся с использованием индекса поиска. Это выпуск SQL Server 2008 R2 Enterprise.

Дэвид
источник
В запросе есть ORDER BYпункт. Добавляя TOPизменения, где в плане происходит такая сортировка, но я больше обеспокоен тем, как это влияет на количество выполнений поиска индекса по таблице B ... (конечно, эти два могут быть связаны - я не знаю)
Дэвид
1
Связанное обсуждение: FAST num_rowsподсказка запроса.
Ремус Русану

Ответы:

39

Я бы предположил, что когда запрос включает TOP n, ядро ​​базы данных будет запускать запрос, игнорируя предложение TOP, а затем в конце просто сократит этот результат до n запрошенных строк. Графический план выполнения, кажется, указывает на то, что это так - TOP является «последним» шагом. Но, похоже, что-то еще происходит.

То, как сформулировано выше, заставляет меня думать, что у вас может быть неправильное представление о том, как выполняется запрос. Оператор в плане запроса не является шагом (где полный набор результатов предыдущего шага оценивается следующим.

SQL Server использует конвейерную модель выполнения, где каждый оператор предоставляет такие методы, как Init () , GetRow () и Close () . Как следует из названия GetRow () , оператор создает одну строку за раз по требованию (как того требует его родительский оператор). Это задокументировано в справочнике по логическим и физическим операторам Books Online , более подробно в моем блоге « Почему планы запросов выполняются задом наперед» . Эта рядовая модель необходима для формирования интуитивной интуиции при выполнении запросов.

У меня вопрос, как (и почему) предложение TOPn влияет на план выполнения запроса?

Некоторые логические операции, такие как TOP, полусоединения и FAST n подсказки запросов, влияют на то, как оптимизатор запросов обходится альтернативам плана выполнения. Основная идея заключается в том, что одна возможная форма плана может возвращать первые n строк быстрее, чем другой план, который был оптимизирован для возврата всех строк.

Например, объединение индексированных вложенных циклов часто является самым быстрым способом возврата небольшого числа строк, хотя объединение хешей или объединение со сканированием может быть более эффективным на больших наборах. Оптимизатор запросов рассуждает об этих вариантах, устанавливая цель строки в определенной точке логического дерева операций.

Цель строки изменяет способ определения альтернатив плана запроса. Суть его в том, что оптимизатор начинает с того, что каждый оператор оценивает стоимость, как если бы требовался полный набор результатов, устанавливает целевую строку в соответствующей точке, а затем возвращается обратно к дереву плана, оценивая количество строк, которые он ожидает проверить. встретить цель строки.

Например, логика TOP(10)устанавливает целевую строку в 10 в конкретной точке логического дерева запросов. Затраты операторов, ведущих к цели строки, модифицируются, чтобы оценить, сколько строк им нужно произвести для достижения цели строки. Этот расчет может стать сложным, так что все это легче понять с помощью полностью проработанного примера и аннотированных планов выполнения. Цели строк могут влиять не только на выбор типа соединения или на то, предпочтительнее ли поиск и поиск, чем на сканирование. Подробнее об этом здесь .

Как всегда, план выполнения, выбранный на основе цели строки, зависит от рассуждений оптимизатора и качества предоставляемой ему информации. Не каждый план с целью получения строк на практике будет производить необходимое количество строк быстрее, но в соответствии с моделью калькуляции это будет происходить.

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

Ваш конкретный запрос и план могут не подходить для подробного анализа здесь (во всех случаях, если хотите, предоставьте фактический план выполнения), но, надеюсь, идеи, изложенные здесь, позволят вам продвинуться вперед.

Пол Уайт говорит, что GoFundMonica
источник
12

Когда вы используете TOP, оптимизатор видит возможность выполнять меньше работы. Если вы попросите 10 рядов, то, скорее всего, вам не понадобится весь набор. Таким образом, оператор TOP может быть перемещен намного дальше вправо. Он будет продолжать запрашивать строки у следующего оператора (справа), пока не получит достаточно.

Вы указываете, что без TOP запрос сортируется в самом конце. Если движок может заранее знать, сколько строк должно быть удовлетворено объединением, он вполне может выбрать аналогичный план, расположив ТОП слева. Но при стремлении сделать Hash Match относительно высоким и, по-видимому, без возможности объединения слиянием, оптимизатор может предпочесть отфильтровать TOP дальше вправо.

Когда запрашивается таблица B, она выбирает по одной строке за раз. Вот почему оценка равна 1. Это также предполагает, что эта строка будет найдена только в 50% случаев. Так что он предполагает, что потребуется 2n + 1, чтобы найти его.

Роб Фарли
источник
Неправильно, что предполагаемое количество строк будет меняться в зависимости от способа извлечения данных. То, как он получает данные, не должно влиять на количество элементов. Изменения в способе его выборки будут отражены в количестве выполнений, верно?
Дэвид
«Расчетное количество строк» ​​- за исполнение. В Nested Loop вполне вероятно, что он будет выполнен несколько раз.
Роб Фарли
Тогда это будет отличаться от фактического количества строк и (фактического) количества казней. Если фактический план показывает 16 834 фактических выполнения и 15 407 фактических возвращенных строк, я предполагаю, что это означает, что он выполнил 16 000 запросов, но нашел только 15 000 запросов, соответствующих предикату. Если бы это означало 15 000 строк на выполнение, это было бы 15 000 * 16 000 = 240 миллионов строк - примерно в 10 раз больше, чем таблица ...
Дэвид
Кроме того, я не уверен, что следую последнему утверждению вашего ответа. Когда вы говорите, что 2n + 1 пытается найти «это», что вы подразумеваете под «этим»? Конечно, нет ни одного ряда? Вы имеете в виду, что оптимизатор предполагает, что для любой данной строки в A есть 50% -ная вероятность, что она будет сопоставлена ​​с B, и поэтому ему нужно будет «попробовать» строки 2003 из A, чтобы получить 1001 совпадений из B? Документируется ли такое поведение где-либо Microsoft? И какое это имеет отношение к TOPпункту? Спасибо за ваш ответ (ы) / терпение.
Дэвид
Да, Расчетные ряды за исполнение. Фактические строки являются общими. Хотя нет проблем с оператором, возвращающим больше строк, чем в таблице, поскольку очень легко продемонстрировать операторы, возвращающие одну и ту же строку несколько раз.
Роб Фарли