ROW_NUMBER () НАД (РОЗДІЛЕННЯ В, ЗАМОВЛЕННЯ В) не використовує індекс на (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 (Build 7601: Service Pack 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 (Build 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

ПАРТІЯ В, А

BA

І те й інше

і те й інше

Як бачимо, другий план має додатковий сортування. Він замовляє B, A, C. Оптимізатор, мабуть, недостатньо розумний, щоб зрозуміти, що PARTITION BY B,Aце те саме, що PARTITION BY A,Bпереробляє дані.

Цікаво, що в третьому запиті є обидва варіанти ROW_NUMBERйого, і додаткового сортування немає! План такий же, як і для першого запиту. (Проект «Послідовність» має додаткове вираження у списку вихідних даних для додаткового стовпця, але немає додаткового сортування). Отже, у цьому більш складному випадку оптимізатор виявився досить розумним, щоб зрозуміти, що PARTITION BY B,Aце те саме PARTITION BY A,B.

У першому та третьому запитах оператор Index Scan має властивість упорядковано: 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);

тоді знову з'являється додатковий Сортування!

Чи може хтось пролити світло? Що тут відбувається в оптимізаторі?


Відповіді:


2

Здається, що немає хорошої остаточної "відповіді" на питання "що відбувається в оптимізаторі", якщо ви не є його розробником і не знаєте його внутрішніх справ.

Я зберу тут коментарі.

Загалом, здається, було б занадто суворо це назвати помилкою, оскільки кінцевий результат запиту правильний. У деяких випадках план виконання просто не є оптимальним. ypercubeᵀᴹ , Мартін Сміт та Аарон Бертран називають це "пропущеною оптимізацією".

  • Схоже, GROUP BY a,bі GROUP BY b,aдає однакові плани, але PARTITION BYне може використовувати те саме перетворення

  • Існують і інші відсутні оптимізації, коли віконні функції з тією ж специфікацією вікна можуть мати додаткову операцію сортування, якщо розділити їх у списку вибору однією з іншою специфікацією.

  • Так, це здається черговою пропущеною оптимізацією, і таких є багато. Оптимізатор написаний людьми і не є ідеальним


Існує дещо пов’язана стаття Зниження індексів. Впорядкування індексів, паралелізм та обчислення рейтингів Іцік Бен-Ган. Там Itzik обговорює низхідні індекси, а також наводить приклад того, як напрямок визначення індексу впливає на функції вікон із розділами. Він показує приклади запитів та генерованих планів, ROW_NUMBERякі мають додатковий оператор сортування, якого оптимізатор міг би уникнути.


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

Інша сторона цієї обережності полягає в тому, що ви переглядаєте свої індекси та вирішуєте поміняти місцями колонки у визначенні індексу. Майте на увазі, що ви можете ненавмисно вплинути на деякі існуючі запити, на які, здавалося б, не повинно впливати. Це власне те, як я помітив цю особливість оптимізатора.

Якщо цього не зробити, оптимізатор може не мати можливості використовувати індекс на повний потенціал. Навіть якщо оптимізатор вибере оптимальний план, такий план може змінитись на менш оптимальний з найменшою невинною зміною запиту, як-от зміна порядку стовпців у SELECTзаяві.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.