Rad*_*hiu 6 sql-server execution-plan sql-server-2008-r2 sql-server-2017
我遇到了一个很奇怪的问题。我正在运行相同的脚本来生成数据并稍后在较旧的 2008R2 实例上进行一些匹配。最后一个查询(一个UPDATE ) 执行单个表扫描并返回所有 250.000 行,而在较新的 2017 实例上,该表是并行扫描的,4 个线程中的每一个都读取 250.000 行,并返回 100 万“实际读取行”。
我在 2017 年的实例中将兼容模式更改为 2008 年,实际值保持不变,为 1.000.000。
为什么会发生这种情况是否有任何正当理由,或者这似乎应该是一个 Connect 项目?
计划包含相同的运算符,但其中一个执行并行扫描,而不是将 250.000 行拆分为 4 个线程中的每一个(并且每个线程仅读取 62.500 行)所有线程读取 250k,每个 *4 = 1.000.000
两个执行计划都可以在 pastetheplan 找到:
此外,我正在运行的完整脚本可以在下面找到:
create table #targets (id int identity(1,1), start_point int, end_point int, refference_type_id int, bla1 int, bla2 int, bla3 int, bla4 int, bla5 int, bla6 int, bla7 int, bla8 int, bla9 bit, assignedTouch varchar(10));
;with cte as (
select
1 sp
, abs(checksum(newid())) % 11 + 2 ep
, 1 rn
, abs(checksum(newid())) % 3 b
, abs(checksum(newid())) % 3 c
, abs(checksum(newid())) % 3 d
, abs(checksum(newid())) % 3 e
, abs(checksum(newid())) % 3 f
, abs(checksum(newid())) % 3 g
, abs(checksum(newid())) % 3 g2
, abs(checksum(newid())) % 3 h
, abs(checksum(newid())) % 3 x3
union all
select
sp
, abs(checksum(newid())) % 11 + 2 ep
, rn + 1
, abs(checksum(newid())) % 8
, abs(checksum(newid())) % 8
, abs(checksum(newid())) % 8
, abs(checksum(newid())) % 8
, abs(checksum(newid())) % 8
, abs(checksum(newid())) % 8
, abs(checksum(newid())) % 8
, abs(checksum(newid())) % 8
, abs(checksum(newid())) % 8
from cte
where rn < 250000)
insert into #targets
select *
, 'Unassigned' [Default State]
from cte
option (maxrecursion 0)
select top 250 *
, char(abs(checksum(newid())) % 85 + 65) [class]
into #matching
from #targets
where end_point in ( 11, 14, 22, 33 )
order by abs(checksum(newid())) % 13
update #matching
set bla8 = case
when bla1 - bla2 > 0
then NULL
else bla8
end
, bla7 = case
when bla2 - bla3 > 0
then NULL
else bla7
end
, bla6 = case
when bla4 - bla5 > 0
then NULL
else bla6
end
create nonclustered index nc_assignedTouch on #targets (assignedTouch);
update t
set assignedTouch = m.class
from #targets t
inner join #matching m
on t.start_point = isnull(m.start_point, t.start_point)
and t.bla1 = isnull(m.bla1, t.bla1)
and t.bla2 = isnull(m.bla2, t.bla2)
and t.bla3 = isnull(m.bla3, t.bla3)
and t.bla4 = isnull(m.bla4, t.bla4)
and t.bla5 = isnull(m.bla5, t.bla5)
and t.bla6 = isnull(m.bla6, t.bla6)
and t.bla7 = isnull(m.bla7, t.bla7)
and t.bla8 = isnull(m.bla8, t.bla8)
and t.bla9 = isnull(m.bla9, t.bla9)
where t.assignedTouch = 'Unassigned';
Run Code Online (Sandbox Code Playgroud)
sp_configure 信息:
Pau*_*ite 12
您可能希望在并行线程之间进行分配,如下面的玩具示例所示:
SELECT TOP (5 * 1000 * 1000)
n = ROW_NUMBER() OVER (ORDER BY @@SPID)
INTO #n
FROM sys.columns AS C
CROSS JOIN sys.columns AS C2
CROSS JOIN sys.columns AS C3;
SELECT COUNT_BIG(*)
FROM #n AS N
GROUP BY N.n % 10
OPTION (USE HINT ('ENABLE_PARALLEL_PLAN_PREFERENCE'));
Run Code Online (Sandbox Code Playgroud)
在那个计划中,堆表确实是并行扫描的,所有线程协调共享读取整个表的工作:
或在 SSMS 视图中:
但这不是您上传的计划中的安排:
堆扫描位于嵌套循环 join*的内侧,因此每个线程都运行内侧的串行副本,这意味着存在 Table Spool 和 Table Scan 的 DOP(并行度)独立副本。
在 DOP 4 时,这意味着有四个线轴和四个扫描,如Number of Executions = 4桌面扫描所证明的那样。实际上,堆表被完全扫描了四次(每个线程一次),得到 250,000 * 4 = 1,000,000 行。延迟假脱机缓存每个线程的扫描结果。
所以不同之处在于您的并行扫描是并行的四次串行扫描,而不是四个线程合作并行扫描一次堆(如上面的玩具示例)。
将差异概念化可能具有挑战性,但它至关重要。一旦您将两个交易所之间的分支视为 DOP 单独的串行计划,解码就会变得更容易。
当然,该计划效率极低,线轴几乎没有增加任何价值。请注意,连接谓词卡在嵌套循环连接处,而不是被推到内侧(使连接成为应用)。这是由于涉及ISNULL.
你可能会得到一个稍微好一点的计划 nc_assignedTouch索引聚集而不是非聚集,,但大部分工作仍会在连接处发生,而且改进几乎肯定是最小的。这里可能需要重写查询。如果您需要帮助以更易于执行的方式表达查询,请提出后续问题。
有关并行性方面的更多背景信息,请参阅我的文章了解和使用 SQL Server 中的并行性。
*对此有一个普遍的例外,在嵌套循环连接的内侧可以看到真正的协作并行扫描(和交换):必须保证外部输入最多产生一行,并且循环join 不能有任何相关参数(外部引用)。在这些条件下,执行引擎将允许内部并行,因为它总是会产生正确的结果。
您可能还会注意到,并行嵌套循环连接内侧的 Eager Spool 下方的运算符也只执行一次。这些运算符仍然有 DOP 副本,但运行时行为是只有一个线程构建一个共享索引结构,然后由 Eager Spool 的所有实例使用。我很抱歉这一切太复杂了。