Procure e você deve digitalizar ... em tabelas particionadas

22

Eu li esses artigos no PCMag por Itzik Ben-Gan :

Procure e você deverá digitalizar a Parte I: quando o otimizador não otimizar
Buscar e você deverá digitalizar a Parte II: Teclas ascendentes

Atualmente, estou tendo um problema "Máximo agrupado" em todas as nossas tabelas particionadas. Usamos o truque que Itzik Ben-Gan forneceu para obter um máximo (ID), mas às vezes simplesmente não é executado:

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 

Eu recebo esse plano

insira a descrição da imagem aqui

Mas, após 45 minutos, observe as leituras

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

das quais eu saio sp_whoisactive.

Normalmente, ele roda rapidamente, mas não hoje.

Editar: estrutura da tabela com partições:

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
fonte

Respostas:

28

O problema básico é que a busca de índice não é seguida por um operador superior. Essa é uma otimização que geralmente é introduzida quando a busca retorna linhas na ordem correta para um MIN\MAXagregado.

Essa otimização explora o fato de que a linha mínima / máxima é a primeira em ordem crescente ou decrescente. Também pode ser que o otimizador não possa aplicar essa otimização a tabelas particionadas; Eu esqueço.

De qualquer forma, o ponto é que, sem essa transformação, o plano de execução acaba processando todas as linhas qualificadas S.UpdatedID <= @IDColumnThresholdValuepor partição, em vez da linha desejada por partição.

Você não forneceu definições de tabela, índice ou particionamento na pergunta, portanto não posso ser muito mais específico. Você deve verificar se o seu índice suportaria essa transformação. Mais ou menos equivalente, você também pode expressar o MAXcomo a TOP (1) ... ORDER BY UpdatedID DESC.

Se isso resultar em uma classificação (incluindo uma classificação TopN ), você sabe que seu índice não é útil. Por exemplo:

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;

A forma do plano que isso deve produzir é:

Forma do plano desejado

Observe o topo abaixo da busca do índice. Isso limita o processamento a uma linha por partição.

Ou, usando uma tabela temporária para armazenar os números das partições:

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;

Nota lateral: acessar uma tabela do sistema na sua consulta evita o paralelismo. Se isso for importante, considere materializar os números da partição em uma tabela temporária e, a APPLYpartir dela. O paralelismo normalmente não é útil nesse padrão (com indexação correta), mas seria negligente da minha parte não mencioná-lo.

Nota lateral 2: Existe uma item do Connect ativo solicitando suporte MIN\MAXinterno para agregados e Top em objetos particionados.

Paul White diz que a GoFundMonica
fonte