В прошлой статье я писал об особом виде оператора TOP, известного как ROWCOUNT TOP. Теперь рассмотрим несколько других интересных сценариев появления в плане оператора TOP. 

В общем случае, ТОР — довольно приземленный оператор. Он просто подсчитывает и возвращает заданное количество строк. SQL Server 2005 включает в себя два усовершенствования этого оператора, которых не было в SQL Server 2000.

Во-первых, в SQL Server 2000 можно указать только константу в виде целого числа возвращаемых строк. В SQL Server 2005 мы можем указать произвольное выражение, включая выражение, содержащее переменные или параметры T-SQL. 

Во-вторых, SQL Server 2000 допускает только TOP в операторе SELECT (хотя он поддерживает ROWCOUNT TOP в операторах INSERT, UPDATE и DELETE). SQL Server 2005 допускает TOP с операторами SELECT, INSERT, UPDATE и DELETE.

В этой статье мы сосредоточимся на нескольких простых примерах с оператором SELECT. Для начала создадим небольшую таблицу:

CREATE TABLE T (A INT, B INT)
CREATE CLUSTERED INDEX TA ON T(A)
SET NOCOUNT ON
DECLARE @i INT
SET @i = 0
WHILE @i < 100
  BEGIN
    INSERT T VALUES (@i, @i)
    SET @i = @i + 1
  END
SET NOCOUNT OFF

План простейшего запроса с TOP не нуждается в пояснениях:

SELECT TOP 5 * FROM T
Rows   Executes
5      1        |--Top(TOP EXPRESSION:((5)))
5      1             |--Clustered Index Scan(OBJECT:([tempdb].[dbo].[T].[TA]))

TOP часто используется в сочетании с ORDER BY. Сочетание TOP с ORDER BY способствует детерминированности выборки. Без ORDER BY выборка зависит от плана запроса и даже может меняться от выполнения к выполнению. Если у нас есть подходящий индекс для поддержки выбранного порядка строк, план запроса останется простым (обратите внимание на ключевые слова ORDERED FORWARD):

SELECT TOP 5 * FROM T ORDER BY A
Rows   Executes
5      1        |--Top(TOP EXPRESSION:((5)))
5      1             |--Clustered Index Scan(OBJECT:([tempdb].[dbo].[T].[TA]), ORDERED FORWARD)

Обратите внимание что, если нет подходящего индекса для выборки первых 5 строк SQL Server должен просмотреть все 100 строк таблицы. Также обратите внимание, что сортировка в этом сценарии будет «TOP sort». Такая сортировка обычно использует меньше памяти, чем обычная сортировка, поскольку ей нужно прокрутить через алгоритм сортировки только несколько топовых строк, а не всю таблицу.

Теперь давайте рассмотрим, что произойдет, если мы запросим TOP 5% строк. Чтобы это определить. Для получения результата SQL Server должен подсчитать все строки и вычислить 5%. Это делает запросы, использующие TOP PERCENT, менее эффективными, чем запросы, использующие TOP с абсолютным числом строк.

SELECT TOP 5 PERCENT * FROM T
Rows   Executes
5      1        |--Top(TOP EXPRESSION:((5.000000000000000e+000)) PERCENT)
5      1             |--Table Spool
100    1                 |--Clustered Index Scan(OBJECT:([tempdb].[dbo].[T].[TA]))

Как и в предыдущем примере, SQL Server будет просматривать все 100 строк таблицы. Тут SQL Server использует «жадную» очередь (Eager Spool), которая буферизует и подсчитывает все входные строки, прежде чем что-либо возвращать. Затем TOP запрашивает число строк в очереди, вычисляет 5% и продолжает работу, как любой другой TOP.

Если SQL Server в плане запроса должен выполнять сортировку, этим он также может обеспечить подсчёт затронутых строк. Однако только обычная сортировка умеет подсчитывать их количество. Сортировка «TOP sort» должна знать какое число строк необходимо вернуть с самого начала.

SELECT TOP 5 PERCENT * FROM T ORDER BY B
Rows   Executes
5      1        |--Top(TOP EXPRESSION:((5.000000000000000e+000)) PERCENT)
5      1             |--Sort(ORDER BY:([tempdb].[dbo].[T].[B] ASC))
100    1                  |--Clustered Index Scan(OBJECT:([tempdb].[dbo].[T].[TA]))

TOP WITH TIES также несовместим с «TOP sort». TOP WITH TIES не позволяет узнать наверняка, сколько строк будет получено, пока не будет вычитаны все «привязки». В нашем примере давайте сделаем «привязку» для пятой строки:

INSERT T VALUES (4, 4)
SELECT TOP 5 WITH TIES * FROM T ORDER BY B
Rows   Executes
6      1        |--Top(TOP EXPRESSION:((5)))
7      1             |--Sort(ORDER BY:([tempdb].[dbo].[T].[B] ASC))
101    1                  |--Clustered Index Scan(OBJECT:([tempdb].[dbo].[T].[TA]))

В этом представлении плана нет TOP WITH TIES, но при SHOWPLAN_ALL или STATISTICS PROFILE можно увидеть следующее: "TIE COLUMNS:([T].[B])". Это также доступно в графическом и XML-планах запроса для SQL Server 2005. Обратите внимание, что TOP теперь возвращает на одну строку больше. Когда TOP N WITH TIES достигает N-й строки, он хранит копию для привязки значения столбца этой строки (в примере B==4) и сравнивает каждую следующую в выборке строку с этим значением. Если есть подходящие строки, он их все вернёт в результате запроса. Поскольку TOP вынужден сравнивать значения всех оставшихся строк, пока не выберет все совпадения для первых N строк, в нашем примере TOP извлечёт из сортировки на одну строку больше, чем было до него.

Наконец, есть пара вырожденных случаев, когда оптимизатор знает, что TOP 0 и TOP 0 PERCENT никогда ничего не возвращают, и заменяет любой такой план запроса на сканирование константы:

SELECT TOP 0 * FROM T
  |--Constant Scan

Оптимизатор также знает, что TOP 100 PERCENT всегда возвращает все строки и удаляет оператор TOP из плана запроса:

SELECT TOP 100 PERCENT * FROM T
  |--Clustered Index Scan(OBJECT:([tempdb].[dbo].[T].[TA]))

Для этих случаев требуется, чтобы количество строк было постоянным. Например, использование выражения, включающего переменную или параметр T-SQL, приведёт к тому, что план запроса будет такой же, как в общем случае. Оба описанных упрощения плана также работают и с операторами INSERT, UPDATE и DELETE.

Обратите внимание, что не рекомендуется использовать TOP для обхода ограничений языка SQL на использование ORDER BY в подзапросах или представлениях или для принудительного определенных порядка использования операторов в плане запроса.

Комментарии (11)


  1. LuggerFormas
    07.12.2022 17:24
    +2

    https://www.postgresql.org/docs/current/queries-limit.html

    Только криво и про МС.

    https://learn.microsoft.com/ru-ru/sql/t-sql/queries/top-transact-sql?view=sql-server-ver16

    Ах нет, в доках МС тоже все есть.

    ЗАЧЕМ?

    Про 2005 в 2022 даже спрашивать не буду, там уже фоссилизация началась


    1. mssqlhelp Автор
      07.12.2022 18:15
      -1

      Эта серия статей не о языке, а о том, как запросы отображаются в планах и нюансах в этой части. Автор статей один из разработчиков в команде MS SQL Server, т.ч. информация почти из первых рук. Без понимания, как стороятся планы запроса, невозможно заниматься оптимизацией - вот ЗАЧЕМ.


      1. LuggerFormas
        07.12.2022 18:32

        Я прочитал статью. Я ее понял. Но ни одного применения для оптимизации выдумать из нее не могу, даже синтетического.

        Разница между Sort и TOP sort из плана запроса совершенно не ясна. Опять же, очевидно, что если есть "предсортированный" правильно индекс, вполне можно избавиться от сортировки (индекс, кхм....)

        В сухом остатке имею: "Это делает запросы, использующие TOP PERCENT, менее эффективными, чем запросы, использующие TOP с абсолютным числом строк."

        Слегка самоочевидно. Вот почему "ЗАЧЕМ?"

        Если так расписывается отличие 2000 от 2005, то почему нет "в настоящее время"?

        Вырожденные случаи - не пишутся =)


    1. echo10
      08.12.2022 00:39

      what is фоссилизация?


      1. me21
        08.12.2022 11:07

        Превращение в ископаемое, fossil


  1. gleb_l
    07.12.2022 18:55
    +2

    Какие такие «привязки»? Tie означает «ничья» - ср. Tie break. Отсюда with ties - значит с учётом ничьих ;)


    1. mssqlhelp Автор
      08.12.2022 17:35

      Я голову сломал, как это подать в переводе, тут не перевод, а скорее попытка подобрать более - менее подходящее слово. Получилось плохо, но, простите, "ничьих" ещё хуже. Может есть идеи? ...я готов заменить термин...


      1. mssqlhelp Автор
        08.12.2022 17:38

        Кстати, предложено было близко к возможным вариантам: https://translate.google.com/?hl=ru&sl=en&tl=ru&text=Tie&op=translate


  1. Akina
    07.12.2022 22:22

    Вот для меня полнейшая загадка - зачем надо было переводить статью аж пятнадцатилетней давности? А с учётом того, сколько с тех пор версий прошло, с учётом того, что актуальная на момент выхода статьи версия устарела настолько, что упоминается в официальном SQL Server Release Dates and Lifecycle на самом последнем месте, ибо самая в этом списке древняя и вот уже 6 лет как оставшаяся без обслуживания и поддержки со стороны производителя - прямо некрофилия какая-то...

    Я уже даже и не говорю о подозрениях, что в актуальных версиях что-то из сообщённого в этой статье может быть и неверным.


    1. mssqlhelp Автор
      08.12.2022 17:30

      Да нет тут никаких загадок, статья, как и все операторы в планах запросов, актуальны и сейчас, а появились они в SQL Server 2005 (каким кривым он бы и не был и пусть уже и не поддерживается). TOP може быть и не так ценен для оптимизации, но для общего развития вполне себе полезно было про его поведение в планах запросов узнать. Ни автор ни переводчик не декларируют, что это чтиво нужно осилить в обязательном порядке.


      1. mssqlhelp Автор
        08.12.2022 17:33

        Оригиналы этой серии статей не напросно же были перенесены на новый ресурс Майкрософтом в своё время. А сейчас и на этом ресурсе висит банер, что скоро всё пойдёт прахом. Там ещё осталось несколько не переведённых статей Грега, так что уже наберитесь терпения или скипайте эту серию переводов.