Da es sich um einen langjährigen SQL Server für die Produktion handelt, kann ich nicht einfach vorschlagen, Versionen zu aktualisieren
Der Fehler bei der Schätzung der Kardinalität gegen Semi-Joins ist auf allen Versionen von SQL Server von 2005 bis einschließlich 2012 reproduzierbar . Alle benötigen das Trace-Flag 4199, um das Update zu aktivieren. Ein Upgrade würde Ihr Problem also nicht lösen, ohne 4199 zu aktivieren (obwohl es natürlich noch viele andere gute Gründe für ein Upgrade von 2005 gibt).
... als solches kann ich den Traceflag 4199-Hinweis für diese spezielle Abfrage nicht erzwingen.
Wenn nur eine bestimmte Abfrage betroffen ist, können Sie OPTION (QUERYTRACEON 4199)
das Ablaufverfolgungsflag nur für diese Abfrage aktivieren. Dieser Abfragehinweis wird für die Verwendung mit 4199 dokumentiert und unterstützt und gilt ab SQL Server 2005 Service Pack 2.
Dieser Hinweis effektiv läuft DBCC TRACEON (4199)
und DBCC TRACEOFF (4199)
um die Abfrage und erfordert Sysadmin Erlaubnis als Ergebnis. Wenn dies ein Problem ist, fügen Sie den Hinweis mithilfe einer Plananleitung hinzu .
Sie sollten mit 4199 Ihr gesamtes System zu testen aktivierter aussehen auch instanz breit . Planregressionen sind möglich, aber insgesamt können Sie feststellen, dass sich die verschiedenen durch dieses Flag aktivierten Optimierungskorrekturen lohnen. Für alle zukünftigen planbeeinflussenden Abfrageprozessor-Korrekturen muss dieses Flag aktiviert sein.
Alles was gesagt wurde ...
Wie in der Antwort von ypercube erwähnt , müssen für den Fehler zwei oder mehr Verknüpfungsspalten angezeigt werden (neben vielen Details). Die Redundanz in Ihrer NOT IN
Klausel führt dazu, dass der Optimierer zwei Spaltenvergleiche sieht (obwohl es logischerweise nur einen gibt), wodurch der Fehler aufgedeckt wird.
Durch das Entfernen dieser Redundanz wird das Problem für diese bestimmte Abfrage "gelöst", obwohl andere Abfragen, die tatsächlich mehr als ein Join-Prädikat haben, weiterhin anfällig sind .
Beispiel
Zur Veranschaulichung hier ein Beispiel, das auf dem in der Frage verlinkten CSS-Blogbeitrag basiert (jedoch mit einem vollständigen Skript!):
CREATE TABLE dbo.tst_TAB1
(
c1 integer NOT NULL,
c2 integer NOT NULL,
c3 integer NOT NULL
);
CREATE TABLE dbo.tst_TAB2
(
c1 integer NOT NULL,
c2 integer NOT NULL,
c3 integer NOT NULL
);
CREATE INDEX i ON dbo.tst_TAB1 (c1, c2);
CREATE INDEX i ON dbo.tst_TAB2 (c1, c2);
Beispieldaten:
INSERT dbo.tst_TAB1
(c1, c2, c3)
SELECT
number, number, number
FROM master.dbo.spt_values
WHERE
[type] = N'P'
AND number BETWEEN 1 AND 2047;
INSERT dbo.tst_TAB2 (c1, c2, c3)
VALUES (1, 1, 1);
Testabfrage NOT IN
mit redundantem Prädikat:
SELECT
T1.c1
FROM tst_TAB1 AS t1
WHERE
t1.c1 NOT IN
(
SELECT
t2.c1
FROM tst_TAB2 AS t2
-- This is redundant!
WHERE
t2.c1 = t1.c1
);
Der geschätzte Ausführungsplan zeigt eine Schätzung von 1 Zeile nach dem Anti-Semi-Join:
Randnotiz: Tatsächlich ist dies ein Beispiel für einen anderen (seltenen) Fehler. Wenn Sie die WHERE
Klausel t1.c1 = t2.c1
anstelle von schreiben, t2.c1 = t1.c1
kann der Optimierer erkennen, dass die beiden Join-Prädikate tatsächlich identisch sind und der Fehler nicht auftritt.
Die gleiche Abfrage mit OPTION (QUERYTRACEON 4199)
:
SELECT
T1.c1
FROM tst_TAB1 AS t1
WHERE
t1.c1 NOT IN
(
SELECT
t2.c1
FROM tst_TAB2 AS t2
WHERE
t2.c1 = t1.c1
)
OPTION (QUERYTRACEON 4199);
Der geschätzte Ausführungsplan zeigt jetzt eine Schätzung von 2046 Zeilen , was genau richtig ist:
Wir können auch das redundante Prädikat entfernen:
SELECT
T1.c1
FROM tst_TAB1 AS t1
WHERE
t1.c1 NOT IN
(
SELECT
t2.c1
FROM tst_TAB2 AS t2
);
Der Ausführungsplan verwendet zufällig eine zusätzliche, nicht verwandte Optimierung (das Stream-Aggregat). Der wichtige Punkt ist jedoch, dass die Schätzung nach dem Join korrekt ist, ohne dass 4199 aktiviert werden muss:
Mehrere Anti-Semi-Join-Spalten
Es ist möglich, einen Anti-Semi-Join über mehrere Spalten mithilfe der NOT IN
Syntax auszudrücken . Für diese Fälle ist 4199 erforderlich. Beispielsweise wird die nächste Abfrage verknüpft c1
und c2
:
SELECT
T1.c1
FROM tst_TAB1 AS t1
WHERE
t1.c1 NOT IN
(
SELECT
t2.c1
FROM tst_TAB2 AS t2
WHERE
t2.c2 = t1.c2
);
Der Ausführungsplan zeigt die fehlerhafte 1-Zeilen-Schätzung:
Mit 4199 ist das Problem behoben:
SELECT
T1.c1
FROM tst_TAB1 AS t1
WHERE
t1.c1 NOT IN
(
SELECT
t2.c1
FROM tst_TAB2 AS t2
WHERE
t2.c2 = t1.c2
)
OPTION (QUERYTRACEON 4199);
Andere Syntaxen
Die Verwendung NOT IN
auf diese Weise wird am besten vermieden, nicht zuletzt aus den in Books Online genannten Gründen:
Das Problem mit NOT IN
und NULLs
wurde darüber geschrieben , viele Male. Es gibt viele alternative Syntaxen, von denen NOT EXISTS
ich persönlich bevorzugt werde. Beachten Sie, dass durch Ändern der Syntax der Fehler bei der Kardinalitätsschätzung nicht vermieden wird:
SELECT
T1.c1
FROM dbo.tst_TAB1 AS t1
WHERE
NOT EXISTS
(
SELECT 1
FROM dbo.tst_TAB2 AS t2
WHERE
t2.c1 = t1.c1
AND t2.c2 = t1.c2
);
Diese zweispaltige Anti-Semi-Verknüpfung erzeugt die 1-Zeilen-Schätzung und erfordert 4199, um sie zu beheben. Die Ausführungspläne sind genau die gleichen wie zuvor, daher werde ich sie nicht wiederholen. Die NOT EXISTS
Syntax vermeidet das NULLs
Problem mit NOT IN
.
Andere Beobachtungen
Ich stimme den anderen Beobachtungen von ypercube zu.
Das Streuen von NOLOCK
Hinweisen über jede Tabelle in einer Abfrage ist ein schlechter Codegeruch. Wenn die Abfrage die READ UNCOMMITTED
Transaktionssemantik wirklich tolerieren kann , legen Sie die Isolationsstufe explizit fest.
TOP
ohne ORDER BY
ist ein weiteres Zeichen für schlechten Code. TOP
erfordert eine ORDER BY
Klausel, um zu definieren, was TOP
bedeutet. Verlassen Sie sich niemals auf beobachtetes Verhalten, sondern verwenden Sie eine explizite oberste Ebene ORDER BY
, um eine Garantie zu erhalten.
INNER LOOP JOIN
und Verknüpfungshinweise im Allgemeinen implizieren einen FORCE ORDER
Abfragehinweis. Dies schränkt die Freiheit des Optimierers stark ein und wird normalerweise missverstanden und falsch angewendet. Verwenden Sie niemals Hinweise, die Sie nicht vollständig verstehen.