Рассмотрим эти две функции:
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);
Планы выполнения
РАЗДЕЛ А, Б
РАЗДЕЛ B, A
И то и другое
Как видите, у второго плана есть дополнительная сортировка. Заказы по 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);
тогда дополнительная сортировка появляется снова!
Может ли кто-нибудь пролить свет? Что здесь происходит в оптимизаторе?
источник
Ответы:
Кажется, что нет хорошего однозначного «ответа» на вопрос «что происходит в оптимизаторе», если только вы не являетесь его разработчиком и не знаете его внутренности.
Я соберу комментарии здесь.
В целом, кажется, что было бы слишком резко назвать это ошибкой, потому что конечный результат запроса правильный. В некоторых случаях план выполнения просто не оптимален. ypercubeᵀᴹ , Мартин Смит и Аарон Бертран называют это «пропущенной оптимизацией».
Есть несколько связанных статей Descending Indexes. Порядок индексации, параллелизм и ранжирование вычислений Ицик Бен-Ган. Там Ицик обсуждает нисходящие индексы, а также приводит пример того, как направление определения индекса влияет на оконные функции с разделами. Он показывает примеры запросов и сгенерированных планов с
ROW_NUMBER
дополнительным оператором сортировки, которого оптимизатор мог бы избежать.Для меня практическим результатом было бы помнить об этой особенности оптимизатора. При использовании
PARTITION BY
в оконных функциях всегда старайтесь сопоставлять порядок, в котором вы перечисляете столбцы,PARTITION BY
с порядком, в котором они перечислены в индексе. Хотя это не должно иметь значения.Другая сторона этой предосторожности - когда вы просматриваете свои индексы и решаете поменять местами некоторые столбцы в определении индекса. Помните, что вы можете случайно повлиять на некоторые существующие запросы, которые, по-видимому, не должны быть затронуты. Именно так я и заметил эту особенность оптимизатора.
Если вы этого не сделаете, оптимизатор может не использовать индекс в полной мере. Даже если оптимизатор выбирает оптимальный план, такой план может измениться на менее оптимальный с малейшим невинным изменением в запросе, например, с изменением порядка столбцов в
SELECT
выражении.источник