ROW_NUMBER () OVER (PARTITION BY B, A ORDER BY C) не использует индекс для (A, B, C)

12

Рассмотрим эти две функции:

ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C)

ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C)

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

Если есть индекс, (A,B,C)я ожидал, что оптимизатор будет использовать этот индекс в обоих вариантах.

Но, как ни странно, оптимизатор решил сделать дополнительную явную сортировку во втором варианте.

Я видел это на SQL Server 2008 Standard и SQL Server 2014 Express.

Вот полный сценарий, который я использовал для его воспроизведения.

Пробовал на Microsoft SQL Server 2014 - 12.0.2000.8 (X64) 20 февраля 2014 г. 20:04:26 Авторское право (c) Microsoft Corporation Express Edition (64-разрядная версия) в Windows NT 6.1 (сборка 7601: пакет обновления 1)

и Microsoft SQL Server 2014 (SP1-CU7) (KB3162659) - 12.0.4459.0 (X64) 27 мая 2016 г. 15:33:17 Авторское право (c) Microsoft Corporation Express Edition (64-разрядная версия) в Windows NT 6.1 (сборка 7601: служба Пакет 1)

как со старой, так и с новой оценкой кардинальности с помощью OPTION (QUERYTRACEON 9481)и OPTION (QUERYTRACEON 2312).

Настроить таблицу, индекс, пример данных

CREATE TABLE [dbo].[T](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [A] [int] NOT NULL,
    [B] [int] NOT NULL,
    [C] [int] NOT NULL,
    CONSTRAINT [PK_T] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX = OFF, 
STATISTICS_NORECOMPUTE = OFF, 
IGNORE_DUP_KEY = OFF, 
ALLOW_ROW_LOCKS = ON, 
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

CREATE NONCLUSTERED INDEX [IX_ABC] ON [dbo].[T]
(
    [A] ASC,
    [B] ASC,
    [C] ASC
)WITH (PAD_INDEX = OFF, 
STATISTICS_NORECOMPUTE = OFF, 
SORT_IN_TEMPDB = OFF, 
DROP_EXISTING = OFF, 
ONLINE = OFF, 
ALLOW_ROW_LOCKS = ON, 
ALLOW_PAGE_LOCKS = ON)
GO

INSERT INTO [dbo].[T] ([A],[B],[C]) VALUES
(10, 20, 30),
(10, 21, 31),
(10, 21, 32),
(10, 21, 33),
(11, 20, 34),
(11, 21, 35),
(11, 21, 36),
(12, 20, 37),
(12, 21, 38),
(13, 21, 39);

Запросы

SELECT -- AB
    ID,A,B,C
    ,ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C) AS rnAB
FROM T
ORDER BY C
OPTION(RECOMPILE);

SELECT -- BA
    ID,A,B,C
    ,ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C) AS rnBA
FROM T
ORDER BY C
OPTION(RECOMPILE);

SELECT -- both
    ID,A,B,C
    ,ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C) AS rnAB
    ,ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C) AS rnBA
FROM T
ORDER BY C
OPTION(RECOMPILE);

Планы выполнения

РАЗДЕЛ А, Б

AB

РАЗДЕЛ B, A

BA

И то и другое

и то и другое

Как видите, у второго плана есть дополнительная сортировка. Заказы по B, A, C. Оптимизатор, по-видимому, недостаточно умен, чтобы понимать, что PARTITION BY B,Aэто то же самое, что PARTITION BY A,Bи повторная сортировка данных.

Интересно, что в третьем запросе есть оба варианта, ROW_NUMBERи нет дополнительной сортировки! План такой же, как и для первого запроса. (В проекте последовательности есть дополнительное выражение в списке вывода для дополнительного столбца, но нет дополнительной сортировки). Итак, в этом более сложном случае оптимизатор оказался достаточно умным, чтобы понять, что PARTITION BY B,Aэто то же самое, что и PARTITION BY A,B.

В первом и третьем запросах оператор сканирования индекса имеет свойство Ordered: True, во втором запросе значение False.

Еще интереснее, если я напишу третий запрос следующим образом (поменяйте местами два столбца):

SELECT -- both
    ID,A,B,C
    ,ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C) AS rnBA
    ,ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C) AS rnAB
FROM T
ORDER BY C
OPTION(RECOMPILE);

тогда дополнительная сортировка появляется снова!

Может ли кто-нибудь пролить свет? Что здесь происходит в оптимизаторе?

Владимир Баранов
источник
Комментарии в архиве .
Пол Уайт 9

Ответы:

2

Кажется, что нет хорошего однозначного «ответа» на вопрос «что происходит в оптимизаторе», если только вы не являетесь его разработчиком и не знаете его внутренности.

Я соберу комментарии здесь.

В целом, кажется, что было бы слишком резко назвать это ошибкой, потому что конечный результат запроса правильный. В некоторых случаях план выполнения просто не оптимален. ypercubeᵀᴹ , Мартин Смит и Аарон Бертран называют это «пропущенной оптимизацией».

  • Похоже GROUP BY a,bи GROUP BY b,aдает одинаковые планы, но PARTITION BYне может использовать то же преобразование

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

  • Да, это похоже на очередную пропущенную оптимизацию, и таких много. Оптимизатор написан людьми и не совершенен


Есть несколько связанных статей Descending Indexes. Порядок индексации, параллелизм и ранжирование вычислений Ицик Бен-Ган. Там Ицик обсуждает нисходящие индексы, а также приводит пример того, как направление определения индекса влияет на оконные функции с разделами. Он показывает примеры запросов и сгенерированных планов с ROW_NUMBERдополнительным оператором сортировки, которого оптимизатор мог бы избежать.


Для меня практическим результатом было бы помнить об этой особенности оптимизатора. При использовании PARTITION BYв оконных функциях всегда старайтесь сопоставлять порядок, в котором вы перечисляете столбцы, PARTITION BYс порядком, в котором они перечислены в индексе. Хотя это не должно иметь значения.

Другая сторона этой предосторожности - когда вы просматриваете свои индексы и решаете поменять местами некоторые столбцы в определении индекса. Помните, что вы можете случайно повлиять на некоторые существующие запросы, которые, по-видимому, не должны быть затронуты. Именно так я и заметил эту особенность оптимизатора.

Если вы этого не сделаете, оптимизатор может не использовать индекс в полной мере. Даже если оптимизатор выбирает оптимальный план, такой план может измениться на менее оптимальный с малейшим невинным изменением в запросе, например, с изменением порядка столбцов в SELECTвыражении.

Владимир Баранов
источник