Der Index beschleunigt die Ausführung nicht und verlangsamt in einigen Fällen die Abfrage. Wieso ist es so?

34

Ich habe mit Indizes experimentiert, um die Dinge zu beschleunigen, aber im Falle eines Joins verbessert der Index nicht die Ausführungszeit für Abfragen und in einigen Fällen verlangsamt er die Dinge.

Die Abfrage, um eine Testtabelle zu erstellen und mit Daten zu füllen, lautet:

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])

Jetzt ist Abfrage 1, die verbessert wird (nur geringfügig, aber die Verbesserung ist konsistent):

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

Statistik und Ausführungsplan ohne Index (in diesem Fall verwendet die Tabelle den standardmäßigen Clustered-Index):

(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.

Bildbeschreibung hier eingeben

Jetzt mit aktiviertem Index:

(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.

Bildbeschreibung hier eingeben

Nun die Abfrage, die sich aufgrund des Index verlangsamt (die Abfrage ist bedeutungslos, da sie nur zu Testzwecken erstellt wird):

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

Mit aktiviertem Clustered Index:

(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.

Bildbeschreibung hier eingeben

Jetzt mit deaktiviertem Index:

(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.

Bildbeschreibung hier eingeben

Die Fragen sind:

  1. Obwohl der Index vom SQL Server vorgeschlagen wird, warum verlangsamt er die Dinge um einen signifikanten Unterschied?
  2. Was ist der Nested Loop-Join, der die meiste Zeit in Anspruch nimmt und wie kann die Ausführungszeit verbessert werden?
  3. Gibt es etwas, das ich falsch mache oder verpasst habe?
  4. Wenn der Standardindex (nur für Primärschlüssel) weniger Zeit in Anspruch nimmt und der nicht gruppierte Index für jede Zeile in der Verknüpfungstabelle vorhanden ist, sollte die verknüpfte Tabellenzeile schneller gefunden werden, da sich die Verknüpfung in der Spalte Name befindet, in der Der Index wurde erstellt. Dies spiegelt sich im Abfrageausführungsplan wider und die Kosten für die Indexsuche sind geringer, wenn IndexA aktiv ist. Warum jedoch immer noch langsamer? Auch was ist in der Nested Loop der linke äußere Join, der die Verlangsamung verursacht?

Verwenden von SQL Server 2012

SpeedBirdNine
quelle

Antworten:

23

Obwohl der Index vom SQL Server vorgeschlagen wird, warum verlangsamt er die Dinge um einen signifikanten Unterschied?

Indexvorschläge werden vom Abfrageoptimierer erstellt. Wenn eine logische Auswahl aus einer Tabelle gefunden wird, die von einem vorhandenen Index nicht gut bedient wird, fügt sie möglicherweise einen Vorschlag für einen "fehlenden Index" zu ihrer Ausgabe hinzu. Diese Vorschläge sind opportunistisch; Sie basieren nicht auf einer vollständigen Analyse der Abfrage und berücksichtigen keine umfassenderen Überlegungen. Bestenfalls sind sie ein Hinweis darauf, dass eine hilfreichere Indizierung möglich sein könnte, und ein erfahrener DBA sollte dies überprüfen.

Die andere Aussage zu fehlenden Indexvorschlägen ist, dass sie auf dem Kalkulationsmodell des Optimierers basieren und der Optimierer schätzt, um wie viel der vorgeschlagene Index die geschätzten Kosten der Abfrage reduzieren kann . Die Schlüsselwörter sind hier "Modell" und "Schätzungen". Das Abfrageoptimierungsprogramm weiß nur wenig über Ihre Hardwarekonfiguration oder andere Systemkonfigurationsoptionen. Das Modell basiert größtenteils auf festen Zahlen, die für die meisten Benutzer auf den meisten Systemen die meiste Zeit vernünftige Planergebnisse liefern. Abgesehen von Problemen mit den genauen verwendeten Kostennummern sind die Ergebnisse immer Schätzungen - und Schätzungen können falsch sein.

Was ist der Nested Loop-Join, der die meiste Zeit in Anspruch nimmt und wie kann die Ausführungszeit verbessert werden?

Es ist wenig zu tun, um die Leistung des Cross-Join-Vorgangs selbst zu verbessern. Geschachtelte Schleifen sind die einzige physische Implementierung, die für einen Cross-Join möglich ist. Die Tabellenspule auf der Innenseite des Joins ist eine Optimierung, um ein erneutes Scannen der Innenseite für jede äußere Zeile zu vermeiden. Ob dies eine sinnvolle Leistungsoptimierung ist, hängt von verschiedenen Faktoren ab, aber in meinen Tests ist die Abfrage ohne besser dran. Dies ist wiederum eine Folge der Verwendung eines Kostenmodells - meine CPU und mein Speichersystem weisen wahrscheinlich andere Leistungsmerkmale als Ihre auf. Es gibt keinen speziellen Abfragehinweis, um den Tabellenspool zu vermeiden, aber es gibt ein nicht dokumentiertes Ablaufverfolgungsflag (8690), mit dem Sie die Ausführungsleistung mit und ohne Spool testen können. Wenn dies ein echtes Problem des Produktionssystems wäre, Der Plan ohne Spool kann mithilfe einer Planführung erzwungen werden, die auf dem Plan basiert, der mit aktiviertem TF 8690 erstellt wurde. Die Verwendung von nicht dokumentierten Ablaufverfolgungsflags in der Produktion wird nicht empfohlen, da die Installation technisch nicht mehr unterstützt wird und Ablaufverfolgungsflags unerwünschte Nebenwirkungen haben können.

Gibt es etwas, das ich falsch mache oder verpasst habe?

Das Wichtigste, was Sie vermissen, ist, dass der Plan, der den nicht gruppierten Index verwendet, nach dem Modell des Optimierers geringere geschätzte Kosten verursacht, jedoch ein erhebliches Ausführungszeitproblem aufweist. Wenn Sie sich die Verteilung der Zeilen über die Threads im Plan mithilfe des Clustered Index ansehen, sehen Sie wahrscheinlich eine recht gute Verteilung:

Plan scannen

In dem Plan, der Nonclustered Index Seek verwendet, wird die Arbeit letztendlich vollständig von einem Thread ausgeführt:

Plan suchen

Dies ist eine Folge der Art und Weise, wie die Arbeit durch parallele Scan- / Suchvorgänge auf die Threads verteilt wird. Es ist nicht immer der Fall, dass ein paralleler Scan die Arbeit besser verteilt als ein Index-Suchlauf - dies ist jedoch in diesem Fall der Fall. In komplexeren Plänen kann es sein, den Austausch neu zu partitionieren, um die Arbeit auf mehrere Threads zu verteilen. In diesem Plan gibt es keine derartigen Austausche. Sobald einem Thread Zeilen zugewiesen wurden, werden alle zugehörigen Arbeiten an demselben Thread ausgeführt. Wenn Sie sich die Arbeitsverteilung für die anderen Operatoren im Ausführungsplan ansehen, werden Sie feststellen, dass alle Arbeiten von demselben Thread ausgeführt werden, der für die Indexsuche angezeigt wird.

Es gibt keine Abfragetipps, die die Zeilenverteilung zwischen Threads beeinflussen könnten. Wichtig ist, dass Sie sich der Möglichkeit bewusst sind und in der Lage sind, genügend Details im Ausführungsplan zu lesen, um festzustellen, wann es ein Problem verursacht.

Wenn der Standardindex (nur für Primärschlüssel) weniger Zeit in Anspruch nimmt und der nicht gruppierte Index für jede Zeile in der Verknüpfungstabelle vorhanden ist, sollte die verknüpfte Tabellenzeile schneller gefunden werden, da sich die Verknüpfung in der Spalte Name befindet, in der Der Index wurde erstellt. Dies spiegelt sich im Abfrageausführungsplan wider und die Kosten für die Indexsuche sind geringer, wenn IndexA aktiv ist. Warum jedoch immer noch langsamer? Auch was ist in der Nested Loop der linke äußere Join, der die Verlangsamung verursacht?

Es sollte nun klar sein, dass der nicht gruppierte Indexplan potenziell effizienter ist, als Sie es erwarten würden. Es ist nur eine schlechte Verteilung der Arbeit auf die Threads zur Ausführungszeit, die das Leistungsproblem verursacht.

Um das Beispiel zu vervollständigen und einige der oben genannten Dinge zu veranschaulichen, können Sie eine temporäre Tabelle verwenden, um die parallele Ausführung zu steuern:

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;

Dies führt zu einem Plan, der die effizienteren Indexsuchen verwendet, keinen Tabellenspool enthält und die Arbeit gut auf die Threads verteilt:

Optimaler Plan

Auf meinem System wird dieser Plan erheblich schneller ausgeführt als die Version von Clustered Index Scan.

Wenn Sie mehr über die Interna der parallelen Abfrageausführung erfahren möchten, können Sie sich die Aufzeichnung meiner PASS Summit 2013-Sitzung ansehen .

Paul White sagt GoFundMonica
quelle
0

Es ist nicht wirklich eine Frage des Index, es ist eher eine schlecht geschriebene Abfrage. Sie haben nur 100 eindeutige Werte für name. Dadurch verbleibt eine eindeutige Anzahl von 5000 pro Name.

Für jede Zeile in Tabelle 1 verbinden Sie also 5000 aus Tabelle 2. Können Sie sagen, 25020004 Zeilen.

Versuchen Sie dies, beachten Sie, dass dies nur einen Index enthält, den Sie aufgelistet haben.

    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

Und Zeit:

    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.

Bildbeschreibung hier eingeben

Sie können SQL-Indizes nicht für schlecht geformte Abfragen verantwortlich machen

Adrian Sullivan
quelle
1
Danke für die Antwort, und ja, die Abfrage kann verbessert werden, aber die Logik meiner Frage war, dass mit dem Standardindex (nur für Primärschlüssel) weniger Zeit benötigt wird und mit dem nicht gruppierten Index für jede Zeile in In der Verknüpfungstabelle sollte die verknüpfte Tabellenzeile schneller gefunden werden, was sich im Abfrageausführungsplan widerspiegelt und die Index-Suchkosten geringer sind, wenn IndexA aktiv ist. Warum jedoch immer noch langsamer? Auch was ist in der Nested Loop der linke äußere Join, der die Verlangsamung verursacht? Ich habe die Frage bearbeitet, um diesen Kommentar hinzuzufügen und die Frage klarer zu machen.