Шукайте, і ви скануєте… на розділених таблицях


22

Я читав ці статті в PCMag Іціка Бен-Гана :

Шукайте та скануйте частину I: коли оптимізатор не оптимізує пошук,
і ви повинні сканувати частина II: висхідні клавіші

Наразі у мене є проблема "Групований Макс" з усіма нашими розділеними таблицями. Ми використовуємо трюк Itzik Ben-Gan, що надається для отримання максимуму (ID), але іноді він просто не працює:

DECLARE @MaxIDPartitionTable BIGINT
SELECT  @MaxIDPartitionTable = ISNULL(MAX(IDPartitionedTable), 0)
FROM    ( SELECT    *
          FROM      ( SELECT    partition_number PartitionNumber
                      FROM      sys.partitions
                      WHERE     object_id = OBJECT_ID('fct.MyTable')
                                AND index_id = 1
                    ) T1
                    CROSS APPLY ( SELECT    ISNULL(MAX(UpdatedID), 0) AS IDPartitionedTable
                                  FROM      fct.MyTable s
                                  WHERE     $PARTITION.PF_MyTable(s.PCTimeStamp) = PartitionNumber
                                            AND UpdatedID <= @IDColumnThresholdValue
                                ) AS o
        ) AS T2;
SELECT @MaxIDPartitionTable 

Я отримую цей план

введіть тут опис зображення

Але через 45 хвилин подивіться на прочитане

reads          writes   physical_reads
12,949,127        2       12,992,610

що я виходжу sp_whoisactive.

Зазвичай він працює досить швидко, але не сьогодні.

Редагувати: структура таблиці з розділами:

CREATE PARTITION FUNCTION [MonthlySmallDateTime](SmallDateTime) AS RANGE RIGHT FOR VALUES (N'2000-01-01T00:00:00.000', N'2000-02-01T00:00:00.000' /* and many more */)
go
CREATE PARTITION SCHEME PS_FctContractualAvailability AS PARTITION [MonthlySmallDateTime] TO ([Standard], [Standard])
GO
CREATE TABLE fct.MyTable(
    MyTableID BIGINT IDENTITY(1,1),
    [DT1TurbineID] INT NOT NULL,
    [PCTimeStamp] SMALLDATETIME NOT NULL,
    Filler CHAR(100) NOT NULL DEFAULT 'N/A',
    UpdatedID BIGINT NULL,
    UpdatedDate DATETIME NULL
CONSTRAINT [PK_MyTable] PRIMARY KEY CLUSTERED 
(
    [DT1TurbineID] ASC,
    [PCTimeStamp] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, DATA_COMPRESSION = ROW) ON [PS_FctContractualAvailability]([PCTimeStamp])
) ON [PS_FctContractualAvailability]([PCTimeStamp])

GO

CREATE UNIQUE NONCLUSTERED INDEX [IX_UpdatedID_PCTimeStamp] ON [fct].MyTable
(
    [UpdatedID] ASC,
    [PCTimeStamp] ASC
)
INCLUDE (   [UpdatedDate]) 
WHERE ([UpdatedID] IS NOT NULL)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, DATA_COMPRESSION = ROW) ON [PS_FctContractualAvailability]([PCTimeStamp])
GO

Відповіді:


28

Основне питання полягає в тому, що за пошуком індексу Топ-оператор не дотримується. Це оптимізація, яка зазвичай вводиться, коли пошук повертає рядки у правильному порядку для MIN\MAXсукупності.

Ця оптимізація використовує той факт, що рядок min / max є першим у порядку зростання чи зменшення. Можливо також, що оптимізатор не може застосувати цю оптимізацію до розділених таблиць; Я забув.

У будь-якому разі, справа в тому, що без цього перетворення план виконання закінчує обробку кожної рядки, яка відповідає S.UpdatedID <= @IDColumnThresholdValueкожному розділу, а не потрібному одному рядку на розділ.

Ви не вказали визначення таблиці, індексу чи розділів у запитанні, тому я не можу бути набагато конкретнішим. Ви повинні перевірити, чи підтримував би ваш індекс такий перехід. Більш чи менш рівномірно, ви також можете виразити MAXяк a TOP (1) ... ORDER BY UpdatedID DESC.

Якщо це призведе до сортування (включаючи сортування TopN ), ви знаєте, що ваш індекс не корисний. Наприклад:

SELECT
    @MaxIDPartitionTable = ISNULL(MAX(T2.IDPartitionedTable), 0)
FROM    
( 
    SELECT
        O.IDPartitionedTable
    FROM      
    ( 
        SELECT
            P.partition_number AS PartitionNumber
        FROM sys.partitions AS P
        WHERE 
            P.[object_id] = OBJECT_ID(N'fct.MyTable', N'U')
            AND P.index_id = 1
    ) AS T1
    CROSS APPLY 
    (    
        SELECT TOP (1) 
            S.UpdatedID AS IDPartitionedTable
        FROM fct.MyTable AS S
        WHERE
            $PARTITION.PF_MyTable(S.PCTimeStamp) = T1.PartitionNumber
            AND S.UpdatedID <= @IDColumnThresholdValue
        ORDER BY
            S.UpdatedID DESC
    ) AS O
) AS T2;

Форма плану, яку це має створити:

Бажана форма плану

Зауважте зверху під пошуком індексу. Це обмежує обробку одним рядком на розділі.

Або, використовуючи тимчасову таблицю для зберігання номерів розділів:

CREATE TABLE #Partitions
(
    partition_number integer PRIMARY KEY CLUSTERED
);

INSERT #Partitions
    (partition_number)
SELECT
    P.partition_number AS PartitionNumber
FROM sys.partitions AS P
WHERE 
    P.[object_id] = OBJECT_ID(N'fct.MyTable', N'U')
    AND P.index_id = 1;

SELECT
    @MaxIDPartitionTable = ISNULL(MAX(T2.UpdatedID), 0)
FROM #Partitions AS P
CROSS APPLY 
(
    SELECT TOP (1) 
        S.UpdatedID
    FROM fct.MyTable AS S
    WHERE
        $PARTITION.PF_MyTable(S.PCTimeStamp) = P.partition_number
        AND S.UpdatedID <= @IDColumnThresholdValue
    ORDER BY
        S.UpdatedID DESC
) AS T2;

DROP TABLE #Partitions;

Бічна примітка: доступ до системної таблиці у вашому запиті запобігає паралелізму. Якщо це важливо, розгляньте можливість матеріалізації номерів розділів у тимчасовій таблиці, то APPLYз цього. Паралелізм зазвичай не корисний у цій схемі (при правильній індексації), але мені було б відмовити в цьому, не кажучи про це.

Бічна примітка 2: Є активний елемент Connect, який вимагає вбудованої підтримки для MIN\MAXагрегатів та Top на розділених об'єктах.

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