Ищите, и вы должны сканировать ... на многораздельных

22

Я прочитал эти статьи в PCMag Ицик Бен-Ган :

Ищите и вы сканируете Часть I: когда оптимизатор не оптимизирует
Ищите и вы сканируете Часть II: восходящие ключи

В настоящее время у меня проблема «Сгруппированный Макс» со всеми нашими секционированными таблицами. Мы используем трюк, который Ицик Бен-Ган предоставил для получения максимума (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

Основная проблема заключается в том, что за поиском индекса не следует оператор Top. Это оптимизация, которая обычно вводится, когда поиск возвращает строки в правильном порядке для MIN\MAXагрегата.

Эта оптимизация использует тот факт, что строка min / max является первой в порядке возрастания или убывания. Может также оказаться, что оптимизатор не может применить эту оптимизацию к многораздельным таблицам; Я забыл.

В любом случае, дело в том, что без этого преобразования план выполнения завершает обработку каждой строки, которая соответствует S.UpdatedID <= @IDColumnThresholdValueкаждому разделу, а не желаемой одной строке на раздел.

Вы не предоставили в таблице определения таблиц, индексов или разделов, поэтому я не могу быть более конкретным. Вы должны проверить, что ваш индекс будет поддерживать такое преобразование. Более или менее эквивалентно, вы также можете выразить MAXкак 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 для разделенных объектов.

Пол Уайт говорит, что GoFundMonica
источник