Quando i predicati SARGable possono essere inseriti in una tabella CTE o derivata?


15

sacchetto di sabbia

Mentre lavoravo su Blog Posts® di alta qualità, mi sono imbattuto in alcuni comportamenti di ottimizzazione che ho trovato davvero esasperanti . Non ho immediatamente una spiegazione, almeno non una di cui sono contento, quindi la inserisco qui nel caso in cui qualcuno si presenti.

Se vuoi seguire, puoi prendere la versione 2013 del dump di dati Stack Overflow qui . Sto usando la tabella dei commenti, con un indice aggiuntivo su di essa.

CREATE INDEX [ix_ennui] ON [dbo].[Comments] ( [UserId], [Score] DESC );

Query One

Quando eseguo una query sulla tabella in questo modo, ottengo un piano di query dispari .

WITH x
    AS
     (
         SELECT   TOP 101
                  c.UserId, c.Text, c.Score
         FROM     dbo.Comments AS c
         ORDER BY c.Score DESC
     )
SELECT *
FROM   x
WHERE  x.Score >= 500;

NOCCIOLINE

Il predicato SARGable su Score non viene inserito all'interno del CTE. È in un operatore di filtri molto più tardi nel piano.

NOCCIOLINE

Che trovo strano, dal momento che ORDER BYè sulla stessa colonna del filtro.

Query Two

Se cambio la query, viene spinta.

WITH x
    AS
     (
         SELECT   c.UserId, c.Text, c.Score
         FROM     dbo.Comments AS c
     )
SELECT TOP 101 *
FROM   x
WHERE  x.Score >= 500
ORDER BY x.Score DESC;

Anche il piano di query cambia e viene eseguito molto più velocemente, senza versamenti su disco. Entrambi producono gli stessi risultati, con il predicato alla scansione dell'indice non cluster.

NOCCIOLINE

NOCCIOLINE

Query Three

Questo è l'equivalente di scrivere la query in questo modo:

SELECT   TOP 101
         c.UserId, c.Text, c.Score
FROM     dbo.Comments AS c
WHERE c.Score >= 500
ORDER BY c.Score DESC;

Query Four

L'uso di una tabella derivata ottiene lo stesso piano di query "errato" della query CTE iniziale

SELECT *
FROM   (   SELECT   TOP 101
                    c.UserId, c.Text, c.Score
           FROM     dbo.Comments AS c
           ORDER BY c.Score DESC ) AS x
WHERE x.Score >= 500;

Le cose diventano ancora più strane quando ...

Cambio la query per ordinare i dati in ordine crescente e il filtro su <=.

Per non prolungare troppo la domanda, metterò tutto insieme.

Interrogazioni

--Derived table
SELECT *
FROM   (   SELECT   TOP 101
                    c.UserId, c.Text, c.Score
           FROM     dbo.Comments AS c
           ORDER BY c.Score ASC ) AS x
WHERE x.Score <= 500;


--TOP inside CTE
WITH x
    AS
     (
         SELECT   TOP 101
                  c.UserId, c.Text, c.Score
         FROM     dbo.Comments AS c
         ORDER BY c.Score ASC
     )
SELECT *
FROM   x
WHERE  x.Score <= 500;


--Written normally
SELECT   TOP 101
         c.UserId, c.Text, c.Score
FROM     dbo.Comments AS c
WHERE c.Score <= 500
ORDER BY c.Score ASC;

--TOP outside CTE
WITH x
    AS
     (
         SELECT   c.UserId, c.Text, c.Score
         FROM     dbo.Comments AS c
     )
SELECT TOP 101 *
FROM   x
WHERE  x.Score <= 500
ORDER BY x.Score ASC;

Piani

Link al piano .

NOCCIOLINE

Si noti che nessuna di queste query sfrutta l'indice non cluster: l'unica cosa che cambia qui è la posizione dell'operatore di filtro. In nessun caso il predicato viene inviato all'indice.

Appare una domanda!

C'è una ragione per cui un predicato SARGable può essere spinto in alcuni scenari e non in altri? Le differenze tra le query ordinate in ordine decrescente sono interessanti, ma le differenze tra quelle e quelle che stanno crescendo in modo bizzarro.

Per chiunque sia interessato, ecco i piani con solo un indice su Score:

Risposte:


11

Ci sono alcuni problemi in gioco qui.

Spingendo predicati passati TOP

L'ottimizzatore non può attualmente superare un predicato a TOP, anche nei casi limitati in cui sarebbe sicuro farlo *. Questa limitazione tiene conto del comportamento di tutte le query nella domanda in cui il predicato ha un ambito superiore rispetto a TOP.

Il problema consiste nell'eseguire manualmente la riscrittura. Il problema fondamentale è simile al caso di spingere i predicati oltre una funzione di finestra , tranne per il fatto che non esiste una regola specializzata corrispondente SelOnSeqPrj.

La mia opinione personale è che una regola di esplorazione come SelOnToprimane inattuata perché le persone hanno deliberatamente scritto query con lo TOPscopo di fornire una sorta di "recinto di ottimizzazione".

* Generalmente questo significa che il predicato dovrebbe apparire nella ORDER BYclausola associata a TOP, e la direzione di qualsiasi disuguaglianza dovrebbe concordare con la direzione dell'ordinamento. La trasformazione dovrebbe anche tenere conto del comportamento di ordinamento dei NULL in SQL Server. Nel complesso, i limiti probabilmente significano che questa trasformazione non sarebbe generalmente abbastanza utile nella pratica per giustificare gli ulteriori sforzi di esplorazione.

Problemi di costo

I restanti piani di esecuzione nella domanda possono essere spiegati come scelte basate sui costi a causa della distribuzione dei valori nella Scorecolonna (molte più righe <= 500 di> = 500) e l'effetto dell'obiettivo di riga introdotto da TOP.

Ad esempio, la query:

--Written normally
SELECT TOP (101)
    c.UserId, 
    c.[Text],
    c.Score
FROM dbo.Comments AS c
WHERE
    c.Score <= 500
ORDER BY
    c.Score ASC;

... produce un piano con un predicato apparentemente non spremuto in un filtro:

filtro in ritardo a causa dell'obiettivo di fila

Si noti che si stima che l'ordinamento produca 101 righe. Questo è l'effetto dell'obiettivo della riga aggiunto dalla parte superiore. Ciò influisce sul costo stimato dell'ordinamento e del filtro abbastanza da far sembrare che questa sia l'opzione più economica. Il costo stimato di questo piano è di 2401.39 unità.

Se disabilitiamo gli obiettivi di riga con un suggerimento per la query:

--Written normally
SELECT TOP (101)
    c.UserId, 
    c.[Text],
    c.Score
FROM dbo.Comments AS c
WHERE
    c.Score <= 500
ORDER BY
    c.Score ASC
OPTION (USE HINT ('DISABLE_OPTIMIZER_ROWGOAL'));

... il piano di esecuzione prodotto è:

pianificare senza obiettivo di fila

Il predicato è stato inserito nella scansione come predicato residuo non trasferibile e il costo dell'intero piano è di 2402,32 unità.

Si noti che il <= 500predicato non dovrebbe filtrare alcuna riga. Se avessi scelto un numero più piccolo, ad esempio <= 50, l'ottimizzatore avrebbe preferito il piano del predicato push indipendentemente dall'effetto obiettivo della riga.

Per la query con Score DESCe un Score >= 500predicato:

--Written normally
SELECT TOP (101)
    c.UserId, 
    c.[Text],
    c.Score
FROM dbo.Comments AS c
WHERE
    c.Score >= 500
ORDER BY
    c.Score DESC;

Ora il predicato dovrebbe essere molto selettivo, quindi l'ottimizzatore sceglie di spingere il predicato e utilizzare l'indice non cluster con le ricerche:

predicato selettivo

Ancora una volta, l'ottimizzatore ha considerato molteplici alternative e ha scelto questa come l'opzione apparentemente più economica, come al solito.

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.