Cherchez et vous analyserez… sur des tables partitionnées


22

J'ai lu ces articles dans PCMag d' Itzik Ben-Gan :

Recherche et analyse de la partie I: lorsque l'optimiseur n'optimise pas
Recherche et analyse de la partie II: touches ascendantes

J'ai actuellement un problème "Grouped Max" avec toutes nos tables partitionnées. Nous utilisons l'astuce fournie par Itzik Ben-Gan pour obtenir un max (ID), mais parfois cela ne fonctionne tout simplement pas:

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 

Je reçois ce plan

entrez la description de l'image ici

Mais après 45 minutes, regardez les lectures

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

dont je sors sp_whoisactive.

Normalement, cela fonctionne assez rapidement, mais pas aujourd'hui.

Edit: structure de table avec partitions:

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

Réponses:


28

Le problème de base est que la recherche d'index n'est pas suivie par un opérateur Top. Il s'agit d'une optimisation qui est généralement introduite lorsque la recherche renvoie des lignes dans le bon ordre pour un MIN\MAXagrégat.

Cette optimisation exploite le fait que la ligne min / max est la première dans l'ordre croissant ou décroissant. Il se peut également que l'optimiseur ne puisse pas appliquer cette optimisation aux tables partitionnées; J'oublie.

Quoi qu'il en soit, le fait est que sans cette transformation, le plan d'exécution finit par traiter chaque ligne qualifiée S.UpdatedID <= @IDColumnThresholdValuepar partition, plutôt que la ligne souhaitée par partition.

Vous n'avez pas fourni de définitions de table, d'index ou de partitionnement dans la question, je ne peux donc pas être beaucoup plus précis. Vous devez vérifier que votre index prendrait en charge une telle transformation. De façon plus ou moins équivalente, vous pouvez également exprimer le MAXcomme un TOP (1) ... ORDER BY UpdatedID DESC.

Si cela se traduit par un tri (y compris un tri TopN ), vous savez que votre index n'est pas utile. Par exemple:

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;

La forme du plan que cela devrait produire est:

Forme de plan souhaitée

Remarquez le haut sous la recherche d'index. Cela limite le traitement à une ligne par partition.

Ou, en utilisant une table temporaire pour contenir les numéros de partition:

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;

Note latérale: l'accès à une table système dans votre requête empêche le parallélisme. Si cela est important, envisagez de matérialiser les numéros de partition dans une table temporaire, puis à APPLYpartir de cela. Le parallélisme n'est normalement pas utile dans ce modèle (avec une indexation correcte) mais il serait négligent de ma part de ne pas le mentionner.

Note latérale 2: un élément Connect actif demande une prise en charge intégrée pour les MIN\MAXagrégats et Top sur les objets partitionnés.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.