用于并行索引扫描的 STATISTICS IO

i-o*_*one 7 sql-server parallelism database-internals sql-server-2014 scan

假设有一个带有聚集索引的表

create table [a_table] ([key] binary(900) unique clustered);
Run Code Online (Sandbox Code Playgroud)

和一些数据

insert into [a_table] ([key])
select top (1000000) row_number() over (order by @@spid)
from sys.all_columns a cross join sys.all_columns b;
Run Code Online (Sandbox Code Playgroud)

通过检查该表的存储统计

select st.index_level, page_count = sum(st.page_count)
from sys.dm_db_index_physical_stats(
    db_id(), object_id('a_table'), NULL, NULL, 'DETAILED') st
group by rollup (st.index_level)
order by grouping_id(st.index_level), st.index_level desc;
Run Code Online (Sandbox Code Playgroud)

有人能看见

index_level page_count
----------- ----------
8           1
7           7
6           30
5           121
4           487
3           1952
2           7812
1           31249
0           125000
NULL        166659
Run Code Online (Sandbox Code Playgroud)

该表总共需要 166659 页。

然而表扫描

set nocount on;
set statistics io, time on;
declare @cnt int;
select @cnt = count(1) from [a_table];
set statistics io, time off;
Run Code Online (Sandbox Code Playgroud)

产生

Table 'a_table'. Scan count 5, logical reads 484367, ...
CPU time = 1757 ms,  elapsed time = 460 ms.
Run Code Online (Sandbox Code Playgroud)

与表占用的空间相比,逻辑读取数几乎高出三倍。当我检查查询计划时,我注意到 SqlServer 使用了并行索引扫描。这就是问题的第一部分出现的地方。

SqlServer是如何进行并行索引扫描的,做这么多的逻辑读取?

指定option (maxdop 1)抑制并行性

set nocount on;
set statistics io, time on;
declare @cnt2 int;
select @cnt2 = count(1) from [a_table] option (maxdop 1);
set statistics io, time off;
Run Code Online (Sandbox Code Playgroud)

导致

Table 'a_table'. Scan count 1, logical reads 156257, ...
CPU time = 363 ms,  elapsed time = 367 ms.
Run Code Online (Sandbox Code Playgroud)

在这种情况下比较并行和非并行索引扫描的统计数据得出的结论是有时最好避免并行索引扫描。这就是问题的第二部分出现的地方。

我什么时候应该担心并行索引扫描?什么时候应该避免/抑制它?最佳做法是什么?


以上结果是在

Microsoft SQL Server 2014 (SP2) (KB3171021) - 12.0.5000.0 (X64)

Pau*_*ite 7

如果向表中添加一个TABLOCKREADUNCOMMITTED提示,您将获得一个分配顺序扫描,它将报告与表中的页数相同的逻辑读取数。

通过分配顺序扫描,SQL Server 使用分配结构来驱动页面在线程之间的分配。IAM 页面访问不计入STATISTICS IO

对于非分配顺序扫描,SQL Server 使用索引键将扫描分解为子范围。查找新页面或页面范围(基于键范围)并将其分发给工作线程的每个内部操作都需要访问 b 树的上层。这些访问由计数STATISTICS IO,因为是上层存取通过工作线程定位他们的当前范围的起始点时制成。所有这些额外的上层读取解释了您看到的差异。

在我看来,I/O 统计数据太重要了。根据对您和您的工作量而言重要的内容来调整您的查询。这通常是经过的时间,但资源利用率也可能是一个因素。没有一个指标是“最好的”——你应该采取一种平衡的观点。