L'indice non rende l'esecuzione più veloce e in alcuni casi rallenta la query. Perché è così?


34

Stavo sperimentando indici per velocizzare le cose, ma in caso di join, l'indice non migliora il tempo di esecuzione della query e in alcuni casi rallenta le cose.

La query per creare una tabella di prova e riempirla di dati è:

CREATE TABLE [dbo].[IndexTestTable](
    [id] [int] IDENTITY(1,1) PRIMARY KEY,
    [Name] [nvarchar](20) NULL,
    [val1] [bigint] NULL,
    [val2] [bigint] NULL)

DECLARE @counter INT;
SET @counter = 1;

WHILE @counter < 500000
BEGIN
    INSERT INTO IndexTestTable
      (
        -- id -- this column value is auto-generated
        NAME,
        val1,
        val2
      )
    VALUES
      (
        'Name' + CAST((@counter % 100) AS NVARCHAR),
        RAND() * 10000,
        RAND() * 20000
      );

    SET @counter = @counter + 1;
END

-- Index in question
CREATE NONCLUSTERED INDEX [IndexA] ON [dbo].[IndexTestTable]
(
    [Name] ASC
)
INCLUDE (   [id],
    [val1],
    [val2])

Ora la query 1, che è migliorata (solo leggermente ma il miglioramento è coerente) è:

SELECT *
FROM   IndexTestTable I1
       JOIN IndexTestTable I2
            ON  I1.ID = I2.ID
WHERE  I1.Name = 'Name1'

Statistiche e piano di esecuzione senza indice (in questo caso la tabella utilizza l'indice cluster predefinito):

(5000 row(s) affected)
Table 'IndexTestTable'. Scan count 2, logical reads 5580, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 109 ms,  elapsed time = 294 ms.

inserisci qui la descrizione dell'immagine

Ora con Index abilitato:

(5000 row(s) affected)
Table 'IndexTestTable'. Scan count 2, logical reads 2819, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 94 ms,  elapsed time = 231 ms.

inserisci qui la descrizione dell'immagine

Ora la query che rallenta a causa dell'indice (la query non ha senso poiché è stata creata solo per il test):

SELECT I1.Name,
       SUM(I1.val1),
       SUM(I1.val2),
       MIN(I2.Name),
       SUM(I2.val1),
       SUM(I2.val2)
FROM   IndexTestTable I1
       JOIN IndexTestTable I2
            ON  I1.Name = I2.Name
WHERE   
       I2.Name = 'Name1'
GROUP BY
       I1.Name

Con l'indice cluster abilitato:

(1 row(s) affected)
Table 'IndexTestTable'. Scan count 4, logical reads 60, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 1, logical reads 155106, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 17207 ms,  elapsed time = 17337 ms.

inserisci qui la descrizione dell'immagine

Ora con Index disabilitato:

(1 row(s) affected)
Table 'IndexTestTable'. Scan count 5, logical reads 8642, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 2, logical reads 165212, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 17691 ms,  elapsed time = 9073 ms.

inserisci qui la descrizione dell'immagine

Le domande sono:

  1. Anche se l'indice è suggerito da SQL Server, perché rallenta le cose con una differenza significativa?
  2. Qual è il join Nested Loop che impiega la maggior parte del tempo e come migliorare il tempo di esecuzione?
  3. C'è qualcosa che sto facendo di sbagliato o che ho perso?
  4. Con l'indice predefinito (solo sulla chiave primaria) perché impiega meno tempo e con l'indice non cluster presente, per ogni riga nella tabella di join, la riga della tabella unita dovrebbe essere trovata più velocemente, perché join è nella colonna Nome in cui l'indice è stato creato. Ciò si riflette nel piano di esecuzione della query e il costo di ricerca dell'indice è inferiore quando IndexA è attivo, ma perché è ancora più lento? Inoltre, cosa c'è nel join esterno sinistro del Nested Loop che sta causando il rallentamento?

Utilizzando SQL Server 2012

Risposte:


23

Anche se l'indice è suggerito da SQL Server, perché rallenta le cose con una differenza significativa?

I suggerimenti sull'indice sono formulati da Query Optimizer. Se si verifica una selezione logica da una tabella che non è ben servita da un indice esistente, potrebbe esserlo aggiungere un suggerimento "indice mancante" al suo output. Questi suggerimenti sono opportunistici; non si basano su un'analisi completa della query e non tengono conto di considerazioni più ampie. Nella migliore delle ipotesi, indicano che potrebbe essere possibile indicizzare più utile e che un DBA esperto dovrebbe dare un'occhiata.

L'altra cosa da dire sui suggerimenti di indice mancanti è che si basano sul modello di costing dell'ottimizzatore e che l'ottimizzatore stima di quanto l'indice suggerito potrebbe ridurre il costo stimato della query. Le parole chiave qui sono "modello" e "stime". Query Optimizer conosce poco la configurazione hardware o altre opzioni di configurazione del sistema: il suo modello si basa in gran parte su numeri fissi che producono risultati ragionevoli per la maggior parte delle persone sulla maggior parte dei sistemi per la maggior parte del tempo. A parte problemi con i numeri di costo esatti utilizzati, i risultati sono sempre stime e le stime possono essere errate.

Qual è il join Nested Loop che impiega la maggior parte del tempo e come migliorare il tempo di esecuzione?

C'è poco da fare per migliorare le prestazioni dell'operazione di cross join stesso; i loop nidificati sono l'unica implementazione fisica possibile per un cross join. La bobina del tavolo sul lato interno del join è un'ottimizzazione per evitare di ripetere la scansione del lato interno per ogni riga esterna. Se questa sia un'utile ottimizzazione delle prestazioni dipende da vari fattori, ma nei miei test la query è migliore senza di essa. Ancora una volta, questa è una conseguenza dell'utilizzo di un modello di costo: la mia CPU e il mio sistema di memoria hanno probabilmente caratteristiche di prestazioni diverse dalla tua. Non esiste un suggerimento di query specifico per evitare lo spool della tabella, ma esiste un flag di traccia non documentato (8690) che è possibile utilizzare per testare le prestazioni di esecuzione con e senza lo spool. Se questo fosse un vero problema del sistema di produzione, il piano senza bobina potrebbe essere forzato utilizzando una guida di piano basata sul piano prodotto con TF 8690 abilitato. L'uso di flag di traccia non documentati in produzione non è consigliato perché l'installazione diventa tecnicamente non supportata e i flag di traccia possono avere effetti collaterali indesiderati.

C'è qualcosa che sto facendo di sbagliato o che ho perso?

La cosa principale che ti manca è che sebbene il piano che utilizza l'indice non cluster abbia un costo stimato inferiore in base al modello dell'ottimizzatore, presenta un significativo problema di tempo di esecuzione. Se osservi la distribuzione delle righe tra i thread nel piano utilizzando l'indice cluster, probabilmente vedrai una distribuzione ragionevolmente buona:

Piano di scansione

Nel piano che utilizza la Ricerca indice non cluster, il lavoro finisce per essere eseguito interamente da un thread:

Cerca un piano

Ciò è una conseguenza del modo in cui il lavoro viene distribuito tra i thread mediante operazioni di scansione / ricerca parallele. Non è sempre possibile che una scansione parallela distribuisca il lavoro meglio di una ricerca di indice, ma in questo caso lo fa. Piani più complessi potrebbero includere scambi di ripartizionamento per ridistribuire il lavoro tra thread. Questo piano non ha tali scambi, quindi una volta assegnate le righe a un thread, tutto il lavoro correlato viene eseguito su quello stesso thread. Se osservi la distribuzione del lavoro per gli altri operatori nel piano di esecuzione, vedrai che tutto il lavoro viene eseguito dallo stesso thread mostrato per la ricerca dell'indice.

Non ci sono suggerimenti per le query che influenzano la distribuzione delle righe tra i thread, l'importante è essere consapevoli della possibilità e poter leggere dettagli sufficienti nel piano di esecuzione per determinare quando sta causando un problema.

Con l'indice predefinito (solo sulla chiave primaria) perché impiega meno tempo e con l'indice non cluster presente, per ogni riga nella tabella di join, la riga della tabella unita dovrebbe essere trovata più velocemente, perché join è nella colonna Nome in cui l'indice è stato creato. Ciò si riflette nel piano di esecuzione della query e il costo di ricerca dell'indice è inferiore quando IndexA è attivo, ma perché è ancora più lento? Inoltre, cosa c'è nel join esterno sinistro del Nested Loop che sta causando il rallentamento?

Ora dovrebbe essere chiaro che il piano di indice non cluster è potenzialmente più efficiente, come ci si aspetterebbe; è solo una scarsa distribuzione del lavoro tra i thread al momento dell'esecuzione che spiega il problema delle prestazioni.

Per completare l'esempio e illustrare alcune delle cose che ho menzionato, un modo per ottenere una migliore distribuzione del lavoro è utilizzare una tabella temporanea per guidare l'esecuzione parallela:

SELECT
    val1,
    val2
INTO #Temp
FROM dbo.IndexTestTable AS ITT
WHERE Name = N'Name1';

SELECT 
    N'Name1',
    SUM(T.val1),
    SUM(T.val2),
    MIN(I2.Name),
    SUM(I2.val1),
    SUM(I2.val2)
FROM   #Temp AS T
CROSS JOIN IndexTestTable I2
WHERE
    I2.Name = 'Name1'
OPTION (FORCE ORDER, QUERYTRACEON 8690);

DROP TABLE #Temp;

Ciò si traduce in un piano che utilizza le ricerche di indice più efficienti, non presenta uno spool di tabella e distribuisce bene il lavoro tra thread:

Piano ottimale

Sul mio sistema, questo piano viene eseguito in modo significativamente più veloce rispetto alla versione di Scansione indice cluster.

Se sei interessato a saperne di più sull'interno dell'esecuzione di query parallele, potresti voler guardare la mia registrazione della sessione PASS Summit 2013 .


0

Non è proprio una domanda dell'indice, è più una query scritta male. Hai solo 100 valori univoci di nome, questo lascia un conteggio univoco di 5000 per nome.

Quindi per ogni riga nella tabella 1 si stanno unendo 5000 dalla tabella 2. Puoi dire 25020004 righe.

Prova questo, nota che è solo con un indice, quello che hai elencato.

    DECLARE @Distincts INT
    SET @Distincts = (SELECT  TOP 1 COUNT(*) FROM IndexTestTable I1 WHERE I1.Name = 'Name1' GROUP BY I1.Name)
    SELECT I1.Name
    , @Distincts
    , SUM(I1.val1) * @Distincts
    , SUM(I1.val2) * @Distincts
    , MIN(I2.Name)
    , SUM(I2.val1)
    , SUM(I2.val2)
    FROM   IndexTestTable I1
    LEFT OUTER JOIN

    (
        SELECT I2.Name
        , SUM(I2.val1) val1
        , SUM(I2.val2) val2
        FROM IndexTestTable I2
        GROUP BY I2.Name
    ) I2 ON  I1.Name = I2.Name
    WHERE I1.Name = 'Name1'
    GROUP BY  I1.Name

E tempo:

    SQL Server parse and compile time: 
       CPU time = 0 ms, elapsed time = 8 ms.
    Table 'IndexTestTable'. Scan count 1, logical reads 31, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

     SQL Server Execution Times:
       CPU time = 0 ms,  elapsed time = 1 ms.

    (1 row(s) affected)
    Table 'IndexTestTable'. Scan count 2, logical reads 62, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

     SQL Server Execution Times:
       CPU time = 16 ms,  elapsed time = 10 ms.

inserisci qui la descrizione dell'immagine

Non è possibile incolpare gli indici SQL per query mal formate


1
Grazie per la risposta, e sì, la query può essere migliorata, ma la logica della mia domanda era che con l'indice predefinito (solo sulla chiave primaria) perché impiega meno tempo e con l'indice non cluster presente, per ogni riga in nella tabella di join, la riga della tabella unita dovrebbe essere trovata più rapidamente, il che si riflette nel piano di esecuzione della query e il costo di ricerca dell'indice è inferiore quando IndexA è attivo, ma perché è ancora più lento? Inoltre, cosa c'è nel join esterno sinistro del Nested Loop che sta causando il rallentamento? Ho modificato la domanda per aggiungere questo commento, per rendere la domanda più chiara.
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.