Szukaj, a będziesz skanować… na podzielonych na partycje tabelach

22

Przeczytałem te artykuły w PCMag autorstwa Itzika Ben-Gana :

Szukaj i skanuj część I: gdy optymalizator nie optymalizuje
Szukaj i skanuj część II: Rosnące klucze

Mam obecnie problem „Zgrupowanego maksimum” ze wszystkimi naszymi partycjonowanymi tabelami. Używamy sztuczki, którą Itzik Ben-Gan zapewnił dla uzyskania maksimum (ID), ale czasami po prostu nie działa:

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 

Mam ten plan

wprowadź opis zdjęcia tutaj

Ale po 45 minutach spójrz na odczyty

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

z którego wychodzę sp_whoisactive.

Zwykle działa dość szybko, ale nie dzisiaj.

Edycja: struktura tabeli z partycjami:

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
Henrik Staun Poulsen
źródło

Odpowiedzi:

28

Podstawową kwestią jest to, że po wyszukiwaniu indeksu nie występuje operator Top. Jest to optymalizacja, która jest zwykle wprowadzana, gdy funkcja seek zwraca wiersze we właściwej kolejności dla MIN\MAXagregacji.

Ta optymalizacja wykorzystuje fakt, że wiersz min./maks. Jest pierwszym w kolejności rosnącej lub malejącej. Może być również tak, że optymalizator nie może zastosować tej optymalizacji do tabel podzielonych na partycje; Zapominam.

W każdym razie chodzi o to, że bez tej transformacji plan wykonania kończy przetwarzanie każdego wiersza kwalifikującego się S.UpdatedID <= @IDColumnThresholdValuena partycję, a nie pożądanego wiersza na partycję.

W pytaniu nie podano definicji tabeli, indeksu ani partycjonowania, więc nie mogę być bardziej szczegółowy. Powinieneś sprawdzić, czy Twój indeks będzie obsługiwał taką transformację. Mniej więcej tak samo możesz wyrazić MAXjako TOP (1) ... ORDER BY UpdatedID DESC.

Jeśli spowoduje to Sortowanie (w tym Sortowanie TopN ), wiesz, że Twój indeks nie jest pomocny. Na przykład:

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;

Kształt planu, który powinien wytworzyć, to:

Pożądany kształt planu

Zwróć uwagę na górę poniżej Szukania indeksu. Ogranicza to przetwarzanie do jednego wiersza na partycję.

Lub używając tabeli tymczasowej do przechowywania numerów partycji:

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;

Uwaga dodatkowa: dostęp do tabeli systemowej w zapytaniu zapobiega równoległości. Jeśli jest to ważne, rozważ zmaterializowanie numerów partycji w tabeli tymczasowej, a następnie APPLYna tej podstawie. Równoległość zwykle nie jest pomocna w tym schemacie (przy prawidłowym indeksowaniu), ale nie wspominam o tym, żebym o tym nie wspominał.

Uwaga dodatkowa 2: Aktywny element Connect prosi o wbudowane wsparcie dla MIN\MAXagregatów i Top na partycjonowanych obiektach.

Paul White mówi GoFundMonica
źródło