Warum erzwingt die Tabellenvariable einen Index-Scan, während die temporäre Tabelle die Suche und die Lesezeichensuche verwendet?

18

Ich versuche zu verstehen, warum die Verwendung einer Tabellenvariablen den Optimierer daran hindert, eine Indexsuche und dann eine Lesezeichensuche im Vergleich zu einer Indexsuche durchzuführen.

Auffüllen der Tabelle:

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) 

Füllen Sie eine Tabellenvariable mit einem einzelnen Datensatz und versuchen Sie, den Primärschlüssel und die zweite Spalte nachzuschlagen, indem Sie in der Fremdschlüsselspalte suchen:

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

Unten ist der Ausführungsplan:

Bildbeschreibung hier eingeben

Nun die gleiche Abfrage mit einer temporären Tabelle:

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

Dieser Abfrageplan verwendet eine Suche und eine Lesezeichensuche:

Bildbeschreibung hier eingeben

Warum ist der Optimierer bereit, die Lesezeichensuche mit der temporären Tabelle, aber nicht mit der Tabellenvariablen durchzuführen?

Die Tabellenvariable wird in diesem Beispiel verwendet, um Daten darzustellen, die über einen benutzerdefinierten Tabellentyp in einer gespeicherten Prozedur eingehen.

Mir ist klar, dass die Indexsuche möglicherweise nicht geeignet ist, wenn der Fremdschlüsselwert hunderttausend Mal vorkommt. In diesem Fall wäre ein Scan wahrscheinlich die bessere Wahl. Für das von mir erstellte Szenario gab es keine Zeile mit dem Wert 10. Ich finde das Verhalten immer noch interessant und würde gerne wissen, ob es einen Grund dafür gibt.

SQL-Geige

Das Hinzufügen OPTION (RECOMPILE)hat das Verhalten nicht geändert. Der UDDT hat einen Primärschlüssel.

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

8kb
quelle

Antworten:

15

Der Grund für das Verhalten ist, dass SQL Server nicht bestimmen kann, wie viele Zeilen mit ForeignKey übereinstimmen, da kein Index mit RowKey als führende Spalte vorhanden ist (dies kann aus Statistiken in der Tabelle #temp abgeleitet werden, diese jedoch nicht gibt es für Tabellenvariablen (UDTTs), so dass es eine Schätzung von 100.000 Zeilen gibt, was bei einem Scan besser gehandhabt wird als bei einer Suche + Suche. Wenn SQL Server feststellt, dass nur eine Zeile vorhanden ist, ist es zu spät.

Möglicherweise können Sie Ihr UDTT anders konstruieren. In neueren Versionen von SQL Server können Sie Sekundärindizes für Tabellenvariablen erstellen. Diese Syntax ist in 2008 R2 jedoch nicht verfügbar.

Übrigens können Sie das Suchverhalten (zumindest in meinen begrenzten Versuchen) erhalten, wenn Sie versuchen, die Bitmap / den Test zu vermeiden, indem Sie einen Join mit verschachtelten Schleifen andeuten:

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

Diesen Trick habe ich vor einigen Jahren von Paul White gelernt . Natürlich sollten Sie vorsichtig sein, wenn Sie Verknüpfungshinweise in den Produktionscode einfügen. Dies kann fehlschlagen, wenn Benutzer Änderungen an den zugrunde liegenden Objekten vornehmen und diese bestimmte Verknüpfungsart nicht mehr möglich oder nicht mehr optimal ist.

Bei komplexeren Abfragen und wenn Sie zu SQL Server 2012 oder höher wechseln, kann das Ablaufverfolgungsflag 2453 hilfreich sein. Diese Markierung hat bei dieser einfachen Verknüpfung jedoch nicht geholfen. Und es gelten dieselben Haftungsausschlüsse - dies ist nur eine Alternative, auf die Sie im Allgemeinen nicht verzichten sollten, wenn eine Menge Dokumentation und strenge Regressionstests vorhanden sind.

Service Pack 1 wird auch nicht mehr unterstützt. Sie sollten Service Pack 3 + MS15-058 nutzen .

Aaron Bertrand
quelle
3

Tabellenvariablen und temporäre Tabellen werden auf verschiedene Arten unterschiedlich behandelt. Hier gibt es eine großartige Antwort mit vielen Einzelheiten darüber, wo sie sich unterscheiden.

Insbesondere in Ihrem Fall würde ich vermuten, dass die Tatsache, dass temporäre Tabellen zusätzliche Statistiken und parallele Pläne haben können, während Tabellenvariablen begrenzte Statistiken (keine Statistiken auf Spaltenebene) und keine parallelen Pläne haben, Ihre Schuld ist.

Möglicherweise ist es besser, die Tabellenvariable für die Dauer der gespeicherten Prozedur in einer temporären Tabelle abzulegen.

Kenneth Fisher
quelle