Joe*_*ish 9 sql-server parallelism execution-plan sql-server-2017
考虑以下查询,该查询对少量标量聚合进行了反透视:
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);
Run Code Online (Sandbox Code Playgroud)
在 SQL Server 2017 上,我得到了一个包含两个并行分支的计划。左边的平行分支对我来说感觉不合适。优化器保证全局标量聚合只有单行输出,但它的父操作符是具有循环分区的分布式流:
当我执行查询时,所有行都按预期转到单个线程。这个查询没有性能问题,但是查询保留了 8 个并行线程,MAXDOP 设置为 4。同样,我觉得这不合适。两个并行分支同时执行是不可能的。我想避免不必要的工作线程预留,因为我启用了 TF 2467,它更改了调度算法以查看每个调度程序的工作线程数。
是否可以将查询重写为仅包含一个包含表扫描和本地聚合的并行分支?例如,我可以使用下面的一般形状,只是我希望嵌套循环在串行区域中执行:
对于 Application Reasons™,我强烈希望避免将此查询拆分为多个部分。如果需要,您可以在此处查看实际的查询计划。如果你想在家里玩,这里是 T-SQL 来创建查询中使用的表:
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;
Run Code Online (Sandbox Code Playgroud)
当以下所有条件都为真时,我能够通过串行循环连接获得所需的计划形状:
APPLY
orCROSS JOIN
代替UNPIVOT
APPLY
不包含外部引用APPLY
是表值构造函数而不是表例如,这是一种方法:
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);
Run Code Online (Sandbox Code Playgroud)
我只用一个平行分支就得到了所需的计划形状:
我尝试了许多其他不起作用的东西。这个答案并不令人满意,因为我不知道它为什么起作用,并且它可能在 SQL Server 的未来版本中不起作用,但它确实解决了我的问题。
两个并行分支同时执行是不可能的。
执行从计划的左边缘开始。当表扫描分支运行时,嵌套循环分支正在运行(打开,等待数据)。这是不可避免的。两个分支同时处于活动状态,因此 SQL Server 将为此计划预留2 * DOP 工作人员。
对于稳健的解决方案,您可以将数据透视放在表值函数中:
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;
Run Code Online (Sandbox Code Playgroud)
然后将查询重写为:
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;
Run Code Online (Sandbox Code Playgroud)
该函数根据需要使用具有单个分支的并行性:
顶层执行计划是: