Perché la variabile di tabella impone una scansione dell'indice mentre la tabella temporanea utilizza la ricerca di ricerca e segnalibro?


18

Sto cercando di capire perché l'utilizzo di una variabile di tabella impedisce all'ottimizzatore di utilizzare una ricerca indice e quindi la ricerca dei segnalibri rispetto a una scansione dell'indice.

Popolazione della tabella:

CREATE TABLE dbo.Test 
(
    RowKey INT NOT NULL PRIMARY KEY, 
    SecondColumn CHAR(1) NOT NULL DEFAULT 'x',
    ForeignKey INT NOT NULL 
) 

INSERT dbo.Test 
(
    RowKey, 
    ForeignKey
) 
SELECT TOP 1000000 
    ROW_NUMBER() OVER (ORDER BY (SELECT 0)),
    ABS(CHECKSUM(NEWID()) % 10)     
FROM sys.all_objects s1
CROSS JOIN sys.all_objects s2 

CREATE INDEX ix_Test_1 ON dbo.Test (ForeignKey) 

Popolare una variabile di tabella con un singolo record e prova a cercare la chiave primaria e la seconda colonna cercando nella colonna chiave esterna:

DECLARE @Keys TABLE (RowKey INT NOT NULL) 

INSERT @Keys (RowKey) VALUES (10)

SELECT 
    t.RowKey,
    t.SecondColumn
FROM
    dbo.Test t 
INNER JOIN 
    @Keys k
ON
    t.ForeignKey = k.RowKey

Di seguito è riportato il piano di esecuzione:

inserisci qui la descrizione dell'immagine

Ora invece la stessa query utilizzando una tabella temporanea:

CREATE TABLE #Keys (RowKey INT NOT NULL) 

INSERT #Keys (RowKey) VALUES (10) 

SELECT 
    t.RowKey,
    t.SecondColumn
FROM
    dbo.Test t 
INNER JOIN 
    #Keys k
ON
    t.ForeignKey = k.RowKey

Questo piano di query utilizza una ricerca di ricerca e segnalibro:

inserisci qui la descrizione dell'immagine

Perché l'ottimizzatore è disposto a cercare il segnalibro con la tabella temporanea, ma non con la variabile della tabella?

La variabile di tabella viene utilizzata in questo esempio per rappresentare i dati provenienti da un tipo di tabella definito dall'utente in una procedura memorizzata.

Mi rendo conto che la ricerca dell'indice potrebbe non essere appropriata se il valore della chiave esterna si verificava centinaia di migliaia di volte. In tal caso, una scansione sarebbe probabilmente una scelta migliore. Per lo scenario che ho creato, non vi era alcuna riga con un valore di 10. Penso ancora che il comportamento sia interessante e vorrei sapere se c'è una ragione per questo.

SQL Fiddle

L'aggiunta OPTION (RECOMPILE)non ha modificato il comportamento. L'UDDT ha una chiave primaria.

@@VERSION è SQL Server 2008 R2 (SP2) - 10.50.4042.0 (X64) (Build 7601: Service Pack 1) (Hypervisor)

Risposte:


15

Il motivo del comportamento è che SQL Server non è in grado di determinare quante righe corrisponderanno a ForeignKey, poiché non esiste alcun indice con RowKey come colonna principale (è possibile dedurlo dalle statistiche nella tabella #temp, ma quelle non lo fanno esiste per le variabili di tabella / UDTT), quindi effettua una stima di 100.000 righe, che è meglio gestita con una scansione piuttosto che una ricerca + ricerca. Quando SQL Server si rende conto che esiste solo una riga, è troppo tardi.

Potresti essere in grado di costruire l'UDTT in modo diverso; nelle versioni più moderne di SQL Server è possibile creare indici secondari sulle variabili di tabella, ma questa sintassi non è disponibile nel 2008 R2.

A proposito, puoi ottenere il comportamento di ricerca (almeno nelle mie prove limitate) se provi a evitare la bitmap / sonda suggerendo un loop di cicli nidificati:

DECLARE @Keys TABLE (RowKey INT PRIMARY KEY); -- can't hurt

INSERT @Keys (RowKey) VALUES (10);

SELECT 
     t.RowKey
    ,t.SecondColumn
FROM
    dbo.Test t 
INNER JOIN 
    @Keys k
ON
    t.ForeignKey = k.RowKey
    OPTION (LOOP JOIN);

Ho imparato questo trucco da Paul White diversi anni fa. Ovviamente, dovresti stare attento a inserire qualsiasi tipo di suggerimento di join nel codice di produzione: ciò può fallire se le persone apportano modifiche agli oggetti sottostanti e quel tipo specifico di join non è più possibile o non è più ottimale.

Per query più complesse e quando si passa a SQL Server 2012 o versioni successive, è possibile che il flag di traccia 2453 possa essere di aiuto. Quella bandiera non ha aiutato con questo semplice join, però. E si applicherebbero le stesse dichiarazioni di non responsabilità: questa è solo una cosa alternativa che non dovresti fare in genere senza una tonnellata di documentazione e rigorose procedure di test di regressione in atto.

Inoltre, il Service Pack 1 non è più supportato, dovresti ottenere il Service Pack 3 + MS15-058 .


3

Le variabili di tabella e le tabelle temporanee sono gestite in modo diverso in vari modi. C'è un'ottima risposta qui con molti dettagli su dove sono diversi.

In particolare, nel tuo caso, suppongo che il fatto che le tabelle temporanee possano avere statistiche aggiuntive generate e piani paralleli mentre le variabili di tabella abbiano statistiche più limitate (nessuna statistica a livello di colonna) e nessun piano parallelo sia il tuo colpevole.

Potrebbe essere molto meglio scaricare la variabile della tabella in una tabella temporanea per la durata della procedura memorizzata.

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.