Wie kann ich einen nicht hilfreichen parallelen Zweig entfernen, wenn ich eine einzelne Zeile aufhebe?

9

Betrachten Sie die folgende Abfrage, mit der einige Handvoll skalarer Aggregate entfernt werden:

SELECT A, B
FROM (
    SELECT 
      MAX(CASE WHEN ID = 1 THEN 1 ELSE 0 END) VAL1
    , MAX(CASE WHEN ID = 2 THEN 1 ELSE 0 END) VAL2
    , MAX(CASE WHEN ID = 3 THEN 1 ELSE 0 END) VAL3
    , MAX(CASE WHEN ID = 4 THEN 1 ELSE 0 END) VAL4
    , MAX(CASE WHEN ID = 5 THEN 1 ELSE 0 END) VAL5
    , MAX(CASE WHEN ID = 6 THEN 1 ELSE 0 END) VAL6
    , MAX(CASE WHEN ID = 7 THEN 1 ELSE 0 END) VAL7
    , MAX(CASE WHEN ID = 16 THEN 1 ELSE 0 END) VAL16
    FROM dbo.PARALLEL_ZONE_REPRO
) q
UNPIVOT(B FOR A IN (
    VAL1
    ,VAL2
    ,VAL3
    ,VAL4
    ,VAL5
    ,VAL6
    ,VAL7
    ,VAL16
)) U
OPTION (MAXDOP 4);

Unter SQL Server 2017 erhalte ich einen Plan mit zwei parallelen Zweigen. Der linke Parallelast fühlt sich für mich fehl am Platz an. Der Optimierer hat die Garantie, dass nur eine einzige Zeile vom globalen Skalaraggregat ausgegeben wird. Der übergeordnete Operator davon ist jedoch ein Distribute Streams mit Round-Robin-Partitionierung:

Round Robin

Wenn ich die Abfrage ausführe, gehen alle Zeilen wie erwartet zu einem einzelnen Thread. Es gibt kein Leistungsproblem mit dieser Abfrage, aber die Abfrage reserviert 8 parallele Threads mit MAXDOP auf 4. Auch hier bin ich der Meinung, dass dies nicht am richtigen Ort ist. Es ist unmöglich, dass beide parallelen Zweige gleichzeitig ausgeführt werden. Ich möchte unnötige Reservierungen von Arbeitsthreads vermeiden, da TF 2467 aktiviert ist, wodurch der Planungsalgorithmus geändert wird, um die Anzahl der Arbeitsthreads pro Planer anzuzeigen.

Ist es möglich, die Abfrage so umzuschreiben, dass genau ein paralleler Zweig vorhanden ist, der den Tabellenscan und das lokale Aggregat enthält? Zum Beispiel würde ich mit der folgenden allgemeinen Form gut zurechtkommen, außer dass ich möchte, dass die verschachtelte Schleife in einer seriellen Zone ausgeführt wird:

Geben Sie hier die Bildbeschreibung ein

Aus Anwendungsgründen ™ bevorzuge ich dringend, diese Abfrage nicht in Teile aufzuteilen. Auf Wunsch können Sie hier den aktuellen Abfrageplan anzeigen . Wenn Sie zu Hause mitspielen möchten, finden Sie hier T-SQL, um die in der Abfrage verwendete Tabelle zu erstellen:

DROP TABLE IF EXISTS dbo.PARALLEL_ZONE_REPRO;

CREATE TABLE dbo.PARALLEL_ZONE_REPRO (
    ID BIGINT,
    FILLER VARCHAR(100)
);

INSERT INTO dbo.PARALLEL_ZONE_REPRO WITH (TABLOCK)
SELECT
  ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) % 15
, REPLICATE('Z', 100)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;
Joe Obbish
quelle

Antworten:

8

Ich kann die gewünschte Planform mit einem seriellen Loop-Join erhalten, wenn alle der folgenden Bedingungen erfüllt sind:

  • Ein APPLYoder CROSS JOINwird anstelle von verwendetUNPIVOT
  • Das APPLYenthält keine äußeren Referenzen
  • Die Quelle der Zeilen in APPLYist ein Tabellenwertkonstruktor im Gegensatz zu einer Tabelle

Hier ist zum Beispiel eine Möglichkeit, dies zu tun:

SELECT A, B
FROM 
(
    SELECT A
    , MAX(
        CASE
            WHEN A = 'VAL1' THEN VAL1 
            WHEN A = 'VAL2' THEN VAL2
            WHEN A = 'VAL3' THEN VAL3
            WHEN A = 'VAL4' THEN VAL4
            WHEN A = 'VAL5' THEN VAL5
            WHEN A = 'VAL6' THEN VAL6
            WHEN A = 'VAL7' THEN VAL7
            WHEN A = 'VAL16' THEN VAL16
            ELSE NULL
        END
    ) B
    FROM (
         SELECT 
           MAX(CASE WHEN ID = 1 THEN 1 ELSE 0 END) VAL1
         , MAX(CASE WHEN ID = 2 THEN 1 ELSE 0 END) VAL2
         , MAX(CASE WHEN ID = 3 THEN 1 ELSE 0 END) VAL3
         , MAX(CASE WHEN ID = 4 THEN 1 ELSE 0 END) VAL4
         , MAX(CASE WHEN ID = 5 THEN 1 ELSE 0 END) VAL5
         , MAX(CASE WHEN ID = 6 THEN 1 ELSE 0 END) VAL6
         , MAX(CASE WHEN ID = 7 THEN 1 ELSE 0 END) VAL7
         , MAX(CASE WHEN ID = 16 THEN 1 ELSE 0 END) VAL16
         FROM dbo.PARALLEL_ZONE_REPRO
    ) q
    CROSS APPLY (
        VALUES ('VAL1'), ('VAL2'), ('VAL3'), ('VAL4'),
        ('VAL5'), ('VAL6'), ('VAL7'), ('VAL16') 
    ) ca (A)
    GROUP BY A
) q
WHERE q.B IS NOT NULL
OPTION (MAXDOP 4);

Ich erhalte die gewünschte Planplanform, wie behauptet, mit nur einem parallelen Zweig:

Geben Sie hier die Bildbeschreibung ein

Ich habe viele andere Dinge ausprobiert, die nicht funktionierten. Diese Antwort ist insofern unbefriedigend, als ich nicht weiß, warum sie funktioniert und in einer zukünftigen Version von SQL Server möglicherweise nicht funktioniert, aber sie hat mein Problem gelöst.

Joe Obbish
quelle
8

Es ist unmöglich, dass beide parallelen Zweige gleichzeitig ausgeführt werden.

Die Ausführung beginnt am linken Rand des Plans. Der Zweig für verschachtelte Schleifen wird ausgeführt (Öffnen, Warten auf Daten), wenn der Zweig für die Tabellensuche ausgeführt wird. Das ist unvermeidlich . Beide Zweige sind gleichzeitig aktiv, sodass SQL Server 2 * DOP-Mitarbeiter für diesen Plan reserviert .

Für eine robuste Lösung können Sie den Pivot in einer Tabellenfunktion platzieren:

CREATE OR ALTER FUNCTION dbo.PivotPZR()
RETURNS @R table 
(
    VAL1 bigint NOT NULL, VAL2 bigint NOT NULL,
    VAL3 bigint NOT NULL, VAL4 bigint NOT NULL,
    VAL5 bigint NOT NULL, VAL6 bigint NOT NULL,
    VAL7 bigint NOT NULL, VAL16 bigint NOT NULL
)
WITH SCHEMABINDING AS
BEGIN
    DECLARE 
        @Val1 bigint, @Val2 bigint, @Val3 bigint, @Val4 bigint,
        @Val5 bigint, @Val6 bigint, @Val7 bigint, @Val16 bigint;

    -- Can use parallelism
    SELECT
        @Val1 = MAX(CASE WHEN PZR.ID = 1 THEN 1 ELSE 0 END),
        @Val2 = MAX(CASE WHEN PZR.ID = 2 THEN 1 ELSE 0 END),
        @Val3 = MAX(CASE WHEN PZR.ID = 3 THEN 1 ELSE 0 END),
        @Val4 = MAX(CASE WHEN PZR.ID = 4 THEN 1 ELSE 0 END),
        @Val5 = MAX(CASE WHEN PZR.ID = 5 THEN 1 ELSE 0 END),
        @Val6 = MAX(CASE WHEN PZR.ID = 6 THEN 1 ELSE 0 END),
        @Val7 = MAX(CASE WHEN PZR.ID = 7 THEN 1 ELSE 0 END),
        @Val16 = MAX(CASE WHEN PZR.ID = 16 THEN 1 ELSE 0 END)
    FROM dbo.PARALLEL_ZONE_REPRO AS PZR;

    -- Single result row
    INSERT @R
        (VAL1, VAL2, VAL3, VAL4, VAL5, VAL6, VAL7, VAL16)
    VALUES
        (@Val1, @Val2, @Val3, @Val4, @Val5, @Val6, @Val7, @Val16);

    RETURN;
END;

Schreiben Sie dann die Abfrage wie folgt um:

SELECT
    U.A,
    U.B
FROM dbo.PivotPZR() AS PP
UNPIVOT
(
    B FOR A IN (VAL1, VAL2 ,VAL3 ,VAL4, VAL5 ,VAL6 ,VAL7 ,VAL16)
) AS U;

Die Funktion verwendet wie gewünscht Parallelität mit einem einzelnen Zweig:

Funktionsplan

Der Ausführungsplan der obersten Ebene lautet:

Abfrage der obersten Ebene

Paul White 9
quelle