Problemumgehung für Anti-Semi Join-Fehler

7

Ich habe die folgende SQL Server-Abfrage erstellt, aber in SQL Server 2005 tritt ein Anti-Semi-Join-Fehler auf, der zu ungenauen Kardinalitätsschätzungen (1 - urgh!) Führt und für immer ausgeführt wird. Da es sich um einen langjährigen SQL Server für die Produktion handelt, kann ich nicht einfach vorschlagen, Versionen zu aktualisieren, und daher kann ich den Traceflag 4199-Hinweis für diese bestimmte Abfrage nicht erzwingen.

Es fällt mir schwer, das umzugestalten WHERE AND NOT IN (SELECT). Kann jemand helfen? Ich habe sichergestellt, dass versucht wird, die besten Verknüpfungen basierend auf gruppierten Schlüsselpaaren zu verwenden.

SELECT TOP 5000 d.doc2_id
    ,d.direction_cd
    ,a.address_type_cd
    ,d.external_identification
    ,s.hash_value
    ,d.publishdate
    ,d.sender_address_id AS [D2 Sender_Address_id]
    ,a.address_id AS [A Address_ID]
    ,d.message_size
    ,d.subject
    ,emi.employee_id
FROM assentor.emcsdbuser.doc2 d(NOLOCK)
INNER JOIN assentor.emcsdbuser.employee_msg_index emi(NOLOCK)
    ON d.processdate = emi.processdate
    AND d.doc2_id = emi.doc2_id
INNER LOOP JOIN assentor.emcsdbuser.doc2_address a(NOLOCK)
    ON emi.doc2_id = a.doc2_id
    AND emi.address_type_cd = a.address_type_cd
    AND emi.address_id = a.address_id
INNER JOIN sis.dbo.sis s(NOLOCK) ON d.external_identification = s.external_identification
WHERE d.publishdate > '2008-01-01'
    **AND d.doc2_id NOT IN (
        SELECT doc2_id
        FROM assentor.emcsdbuser.doc2_address d2a(NOLOCK)
        WHERE d.doc2_id = d2a.doc2_id
            AND d2a.address_type_cd = 'FRM'
        )**
OPTION (FAST 10)

Beachten Sie, dass die Employee_MSG_IndexTabelle 500 m Zeilen, doc21,5 b Zeilen und SIS~ 500 m Zeilen umfasst.

Jede Hilfe wäre dankbar!

beeks
quelle

Antworten:

13

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 INKlausel 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 INmit 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:

Schätzen Sie 1 Zeile

Randnotiz: Tatsächlich ist dies ein Beispiel für einen anderen (seltenen) Fehler. Wenn Sie die WHEREKlausel t1.c1 = t2.c1anstelle von schreiben, t2.c1 = t1.c1kann 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:

Planen Sie mit TF 4199

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:

Ohne redundantes Prädikat

Mehrere Anti-Semi-Join-Spalten

Es ist möglich, einen Anti-Semi-Join über mehrere Spalten mithilfe der NOT INSyntax auszudrücken . Für diese Fälle ist 4199 erforderlich. Beispielsweise wird die nächste Abfrage verknüpft c1und 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:

Mehrere Verknüpfungsspalten

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

Mehrere Join-Spalten mit 4199

Andere Syntaxen

Die Verwendung NOT INauf diese Weise wird am besten vermieden, nicht zuletzt aus den in Books Online genannten Gründen:

BOL Warnung

Das Problem mit NOT INund NULLswurde darüber geschrieben , viele Male. Es gibt viele alternative Syntaxen, von denen NOT EXISTSich 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 EXISTSSyntax vermeidet das NULLsProblem mit NOT IN.

Andere Beobachtungen

Ich stimme den anderen Beobachtungen von ypercube zu.

  • Das Streuen von NOLOCKHinweisen über jede Tabelle in einer Abfrage ist ein schlechter Codegeruch. Wenn die Abfrage die READ UNCOMMITTEDTransaktionssemantik wirklich tolerieren kann , legen Sie die Isolationsstufe explizit fest.

  • TOPohne ORDER BYist ein weiteres Zeichen für schlechten Code. TOPerfordert eine ORDER BYKlausel, um zu definieren, was TOPbedeutet. Verlassen Sie sich niemals auf beobachtetes Verhalten, sondern verwenden Sie eine explizite oberste Ebene ORDER BY, um eine Garantie zu erhalten.

  • INNER LOOP JOINund Verknüpfungshinweise im Allgemeinen implizieren einen FORCE ORDERAbfragehinweis. 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.

Paul White 9
quelle
6

Der von Ihnen angegebene Link besagt, dass der Fehler nur Verknüpfungen mit mehr als einer Spalte betrifft:

Beachten Sie, dass dieses Problem nur auftritt, wenn im obigen Beispiel mehrere Verknüpfungsspalten an der Verknüpfung beteiligt sind.

Und ich kann nicht verstehen, warum Sie das NOT INauf diese Weise geschrieben haben (indem Sie die d.doc2_id = d2a.doc2_idBedingung in die Unterabfrage einfügen). Es ist redundant (es sei denn, die verknüpften Spalten sind nullwertfähig - oder?), Also könnten Sie das schreiben NOT INals:

AND d.doc2_id NOT IN (
    SELECT d2a.doc2_id
    FROM assentor.emcsdbuser.doc2_address d2a
    WHERE d2a.address_type_cd = 'FRM'
    )

oder mit NOT EXISTS:

AND NOT EXISTS (
    SELECT 1
    FROM assentor.emcsdbuser.doc2_address d2a
    WHERE d.doc2_id = d2a.doc2_id
        AND d2a.address_type_cd = 'FRM'
    )

Versuchen Sie beides und prüfen Sie, ob das Problem mit der Kardinalitätsschätzung behoben ist.

Weitere Hinweise:

  • Haben Sie einen Index für address_type_cd?
  • Warum die Mehrfachnutzung NOLOCK?
  • TOPohne ORDER BYkann es zu unterschiedlichen Ergebnissen pro Ausführung kommen.
ypercubeᵀᴹ
quelle