Cerca e scansionerai ... su tabelle partizionate


22

Ho letto questi articoli in PCMag di Itzik Ben-Gan :

Cercate e Scan Parte I: Quando l'Optimizer non ottimizza
Cercate e scansione Parte II: ascendente Keys

Attualmente sto riscontrando un problema "Grouped Max" con tutte le nostre tabelle partizionate. Usiamo il trucco fornito da Itzik Ben-Gan per ottenere un max (ID), ma a volte non funziona:

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 

Ho questo piano

inserisci qui la descrizione dell'immagine

Ma dopo 45 minuti, guarda le letture

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

da cui esco sp_whoisactive.

Normalmente funziona abbastanza rapidamente, ma non oggi.

Modifica: struttura della tabella con partizioni:

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

Risposte:


28

Il problema di base è che Index Seek non è seguito da un operatore Top. Questa è un'ottimizzazione che viene generalmente introdotta quando la ricerca restituisce le righe nell'ordine corretto per un MIN\MAXaggregato.

Questa ottimizzazione sfrutta il fatto che la riga min / max è la prima in ordine crescente o decrescente. Può anche essere che l'ottimizzatore non possa applicare questa ottimizzazione alle tabelle partizionate; Io dimentico.

Comunque, il punto è che senza questa trasformazione, il piano di esecuzione finisce per elaborare ogni riga che si qualifica S.UpdatedID <= @IDColumnThresholdValueper partizione, piuttosto che quella desiderata per partizione.

Non hai fornito definizioni di tabelle, indici o partizioni nella domanda, quindi non posso essere molto più specifico. Dovresti verificare che il tuo indice supporti tale trasformazione. Più o meno equivalentemente, potresti anche esprimere il MAXsimbolo a TOP (1) ... ORDER BY UpdatedID DESC.

Se questo risulta in un ordinamento (incluso un ordinamento TopN ), sai che il tuo indice non è utile. Per esempio:

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 forma del piano che questo dovrebbe produrre è:

Forma del piano desiderata

Notare la parte superiore sotto la ricerca dell'indice. Ciò limita l'elaborazione a una riga per partizione.

Oppure, utilizzando una tabella temporanea per contenere i numeri di partizione:

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 a margine: l'accesso a una tabella di sistema nella query impedisce il parallelismo. Se questo è importante, considerare la materializzazione dei numeri di partizione in una tabella temporanea, quindi APPLYda quello. Il parallelismo non è di solito utile in questo modello (con una corretta indicizzazione) ma sarebbe una mia negligenza non menzionarlo.

Nota a margine 2: esiste un elemento Connect attivo che richiede il supporto incorporato per gli MIN\MAXaggregati e Top su oggetti partizionati.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.