Comment puis-je me débarrasser d'une branche parallèle inutile lors du retrait d'une seule ligne?

9

Considérez la requête suivante qui détourne quelques poignées d'agrégats scalaires:

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

Sur SQL Server 2017, j'obtiens un plan avec deux branches parallèles. La branche parallèle gauche me semble hors de propos. L'optimiseur a la garantie qu'il n'y aura qu'une seule sortie de ligne de l'agrégat scalaire global, mais l'opérateur parent de celui-ci est un flux de distribution avec partitionnement à tour de rôle:

tournoi à la ronde

Lorsque j'exécute la requête, toutes les lignes vont à un seul thread comme prévu. Il n'y a pas de problème de performances avec cette requête, mais la requête réserve 8 threads parallèles avec MAXDOP défini sur 4. Encore une fois, je pense que cela est hors de propos. Il est impossible pour les deux branches parallèles de s'exécuter en même temps. Je veux éviter la réservation inutile de threads de travail, car j'ai activé TF 2467, ce qui modifie l'algorithme de planification pour examiner le nombre de threads de travail par planificateur.

Est-il possible de réécrire la requête pour avoir exactement une branche parallèle contenant l'analyse de table et l'agrégat local? Par exemple, je serais bien avec la forme générale ci-dessous, sauf que je veux que la boucle imbriquée s'exécute dans une zone série:

entrez la description de l'image ici

Pour Application Reasons ™, je préfère fortement éviter de diviser cette requête en plusieurs parties. Si vous le souhaitez, vous pouvez afficher le plan de requête réel ici . Si vous souhaitez jouer à la maison, voici T-SQL pour créer la table utilisée dans la requête:

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
la source

Réponses:

8

Je peux obtenir la forme de plan souhaitée avec une jointure en boucle série lorsque toutes les conditions suivantes sont remplies:

  • Un APPLYou CROSS JOINest utilisé à la place deUNPIVOT
  • Le APPLYne contient aucune référence externe
  • La source des lignes dans le APPLYest un constructeur de valeur de table par opposition à une table

Par exemple, voici une façon de procéder:

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

J'obtiens la forme de plan souhaitée comme revendiqué avec une seule branche parallèle:

entrez la description de l'image ici

J'ai essayé beaucoup d'autres choses qui n'ont pas fonctionné. Cette réponse n'est pas satisfaisante dans la mesure où je ne sais pas pourquoi cela fonctionne et cela peut ne pas fonctionner dans une future version de SQL Server, mais cela a résolu mon problème.

Joe Obbish
la source
8

Il est impossible pour les deux branches parallèles de s'exécuter en même temps.

L'exécution commence au bord gauche du plan. La branche des boucles imbriquées est en cours d'exécution (ouverture, attente de données) lorsque la branche d'analyse de table est en cours d'exécution. C'est inévitable . Les deux branches sont actives en même temps, SQL Server réservera donc 2 * employés DOP pour ce plan.

Pour une solution robuste, vous pouvez placer le pivot dans une fonction table:

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;

Réécrivez ensuite la requête en:

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;

La fonction utilise le parallélisme avec une seule branche comme vous le souhaitez:

Plan de fonction

Le plan d'exécution de niveau supérieur est le suivant:

Requête de niveau supérieur

Paul White 9
la source