11

Ho un proc memorizzato che cerca prodotti (250.000 righe) utilizzando un indice di testo completo.Perché le prestazioni di queste 2 query sono così diverse?

Il processo memorizzato prende un parametro che è la condizione di ricerca testo completo. Questo parametro può essere nullo, quindi ho aggiunto un controllo Null e la query ha iniziato improvvisamente a eseguire ordini di grandezza più lenti.

-- This is normally a parameter of my stored proc 
DECLARE @Filter VARCHAR(100) 
SET @Filter = 'FORMSOF(INFLECTIONAL, robe)' 

-- #1 - Runs < 1 sec 
SELECT TOP 100 ID FROM dbo.Products 
WHERE CONTAINS(Name, @Filter) 

-- #2 - Runs in 18 secs 
SELECT TOP 100 ID FROM dbo.Products 
WHERE @Filter IS NULL OR CONTAINS(Name, @Filter) 

Qui ci sono i piani di esecuzione:

Query # 1 Execution plant #1

Query # 2 Execution plant #2

Devo ammettere che io non sono molto familiare con i piani di esecuzione. L'unica differenza ovvia per me è che i join sono diversi. Vorrei provare ad aggiungere un suggerimento, ma non avendo aderire alla mia domanda non sono sicuro di come farlo.

Inoltre non capisco perché viene utilizzato l'indice denominato IX_SectionID, poiché si tratta di un indice che contiene solo la SectionID della colonna e che la colonna non è utilizzata in alcun luogo.

risposta

8

OR può schiacciare le prestazioni, in modo da fare in questo modo:

DECLARE @Filter VARCHAR(100) 
SET @Filter = 'FORMSOF(INFLECTIONAL, robe)' 

IF @Filter IS NOT NULL 
BEGIN 
    SELECT TOP 100 ID FROM dbo.Products 
    WHERE CONTAINS(Name, @Filter) 
END 
ELSE 
BEGIN 
    SELECT TOP 100 ID FROM dbo.Products 
END 

un'occhiata a questo articolo: Dynamic Search Conditions in T-SQL by Erland Sommarskog e questa domanda: SQL Server 2008 - Conditional Query.

+0

Nizza articolo - l'aggiunta di 'OPTION (RECOMPILE)' in realtà risolve il problema di prestazioni al 2 ° query (però un altro problema è che 'contains()' solleva un errore quando il parametro è NULL, ma questo è un altro problema). –

1

Hai introdotto una condizione OR. Nella maggior parte dei casi è molto più veloce controllare esplicitamente per NULL ed eseguire una query rispetto al metodo.

Per esempio provate questo:

IF @Filter IS NULL 
BEGIN 
SELECT TOP 100 ID FROM dbo.Products 
END 
ELSE 
BEGIN 
SELECT TOP 100 ID FROM dbo.Products 
WHERE @Filter CONTAINS(Name, @Filter) 
END 
3

Il primo piano di query sembra semplice:

  1. una ricerca a testo integrale per risolvere CONTAINS(Name, @Filter)
  2. un indice di scansione per cercare le altre colonne del righe corrispondenti
  3. combinare i due utilizzando un join hash

Il concatenation operator forma un'unione di due recordset. Quindi sembra che la seconda query sta facendo:

  1. un indice di scansione (in seguito utilizzato per cercare altre colonne)
  2. una scansione costante. Presumo che tratti la tua query come non parametrizzata, quindi il piano di query non deve funzionare per nessun altro valore di @Filter. Se corretta, la scansione costante risolve @Filter is not null.
  3. una ricerca a testo integrale per risolvere CONTAINS(Name, @Filter)
  4. sindacati il ​​risultato di 3 con l'insieme vuoto da 2
  5. ciclo si unisce il risultato di 1 e 4 per cercare le altre colonne

A hash join scambia la memoria per la velocità; se il tuo sistema ha abbastanza memoria, è molto più veloce di un loop join. Questo può facilmente spiegare un rallentamento di 10-100x.

Una soluzione è quella di utilizzare due query distinte:

if @Filter is null 
    SELECT TOP 100 ID FROM dbo.Products 
else 
    SELECT TOP 100 ID FROM dbo.Products WHERE CONTAINS(Name, @Filter) 
+0

Interessante: esiste un modo per forzare un hash join sulla seconda query? –

+0

Sì, potresti riscriverlo usando "inner hash join", ma sarebbe piuttosto complesso. Sarebbe meglio usare una delle soluzioni dell'articolo Sommarskog. – Andomar