稀疏列、cpu 时间和过滤索引

Ran*_*gen 11 sql-server sql-server-2017 sparse-column

稀疏

在对稀疏列进行一些测试时,正如您所做的那样,出现了性能下降,我想知道其直接原因。

数据线

我创建了两个相同的表,一个有 4 个稀疏列,一个没有稀疏列。

--Non Sparse columns table & NC index
CREATE TABLE dbo.nonsparse( ID INT IDENTITY(1,1) PRIMARY KEY NOT NULL,
                      charval char(20) NULL,
                      varcharval varchar(20) NULL,
                      intval int NULL,
                      bigintval bigint NULL
                      );
CREATE INDEX IX_Nonsparse_intval_varcharval
ON dbo.nonsparse(intval,varcharval)
INCLUDE(bigintval,charval);

-- sparse columns table & NC index

CREATE TABLE dbo.sparse( ID INT IDENTITY(1,1) PRIMARY KEY NOT NULL,
                      charval char(20) SPARSE NULL ,
                      varcharval varchar(20) SPARSE NULL,
                      intval int SPARSE NULL,
                      bigintval bigint SPARSE NULL
                      );

CREATE INDEX IX_sparse_intval_varcharval
ON dbo.sparse(intval,varcharval)
INCLUDE(bigintval,charval);
Run Code Online (Sandbox Code Playgroud)

数据管理语言

然后我在两者中插入了大约2540 个非空值。

INSERT INTO dbo.nonsparse WITH(TABLOCK) (charval, varcharval,intval,bigintval)
SELECT 'Val1','Val2',20,19
FROM MASTER..spt_values;

INSERT INTO dbo.sparse WITH(TABLOCK) (charval, varcharval,intval,bigintval)
SELECT 'Val1','Val2',20,19
FROM MASTER..spt_values;
Run Code Online (Sandbox Code Playgroud)

之后,我在两个表中插入了1M 个 NULL

INSERT INTO dbo.nonsparse WITH(TABLOCK)  (charval, varcharval,intval,bigintval)
SELECT TOP(1000000) NULL,NULL,NULL,NULL 
FROM MASTER..spt_values spt1
CROSS APPLY MASTER..spt_values spt2;

INSERT INTO dbo.sparse WITH(TABLOCK) (charval, varcharval,intval,bigintval)
SELECT TOP(1000000) NULL,NULL,NULL,NULL 
FROM MASTER..spt_values spt1
CROSS APPLY MASTER..spt_values spt2;
Run Code Online (Sandbox Code Playgroud)

查询

非稀疏表执行

在新创建的非稀疏表上运行此查询两次时:

SET STATISTICS IO, TIME ON;
SELECT  * FROM dbo.nonsparse
WHERE   1= (SELECT 1) -- force non trivial plan
OPTION(RECOMPILE,MAXDOP 1);
Run Code Online (Sandbox Code Playgroud)

逻辑读取显示5257

(1002540 rows affected)
Table 'nonsparse'. Scan count 1, logical reads 5257, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Run Code Online (Sandbox Code Playgroud)

并且 CPU 时间为343 毫秒

 SQL Server Execution Times:
   CPU time = 343 ms,  elapsed time = 3850 ms.
Run Code Online (Sandbox Code Playgroud)

稀疏表执行

在稀疏表上运行相同的查询两次:

SELECT  * FROM dbo.sparse
WHERE   1= (SELECT 1) -- force non trivial plan
OPTION(RECOMPILE,MAXDOP 1);
Run Code Online (Sandbox Code Playgroud)

读数较低,1763

(1002540 rows affected)
Table 'sparse'. Scan count 1, logical reads 1763, physical reads 3, read-ahead reads 1759, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Run Code Online (Sandbox Code Playgroud)

但是 cpu 时间更高,547 ms

 SQL Server Execution Times:
   CPU time = 547 ms,  elapsed time = 2406 ms.
Run Code Online (Sandbox Code Playgroud)

稀疏表执行计划

非稀疏表执行计划


问题

原始问题

由于NULL值不直接存储在稀疏列中,cpu 时间的增加是否是由于将NULL值作为结果集返回?或者它只是文档中指出的行为?

稀疏列减少了空值的空间需求,但代价是检索非空值的开销更大

还是开销仅与使用的读取和存储有关?

即使在执行选项后使用丢弃结果运行 ssms 时,与非稀疏选择(219 毫秒)相比,稀疏选择的 CPU 时间(407 毫秒)也更高。

编辑

这可能是非空值的开销,即使只有 2540 个,但我仍然不相信。

这似乎是相同的性能,但稀疏因素丢失了。

CREATE INDEX IX_Filtered
ON dbo.sparse(charval,varcharval,intval,bigintval)
WHERE charval IS NULL  
      AND varcharval IS NULL
      AND intval  IS NULL
      AND bigintval  IS NULL;

CREATE INDEX IX_Filtered
ON dbo.nonsparse(charval,varcharval,intval,bigintval)
WHERE charval IS NULL  
      AND varcharval IS NULL
      AND intval  IS NULL
      AND bigintval  IS NULL;


    SET STATISTICS IO, TIME ON;

SELECT  charval,varcharval,intval,bigintval FROM dbo.sparse WITH(INDEX(IX_Filtered))
WHERE charval IS NULL AND  varcharval IS NULL
                     AND intval  IS NULL
                     AND bigintval  IS NULL
                     OPTION(RECOMPILE,MAXDOP 1);


SELECT  charval,varcharval,intval,bigintval 
FROM dbo.nonsparse WITH(INDEX(IX_Filtered))
WHERE charval IS NULL AND 
                      varcharval IS NULL
                     AND intval  IS NULL
                     AND bigintval  IS NULL
                     OPTION(RECOMPILE,MAXDOP 1);
Run Code Online (Sandbox Code Playgroud)

似乎有大约相同的执行时间:

 SQL Server Execution Times:
   CPU time = 297 ms,  elapsed time = 292 ms.

 SQL Server Execution Times:
   CPU time = 281 ms,  elapsed time = 319 ms.
Run Code Online (Sandbox Code Playgroud)

为什么现在逻辑读取量相同?除了包含的 ID 字段和其他一些非数据页面之外,稀疏列的过滤索引不应该存储任何内容吗?

Table 'sparse'. Scan count 1, logical reads 5785,
Table 'nonsparse'. Scan count 1, logical reads 5785
Run Code Online (Sandbox Code Playgroud)

以及两个指数的大小:

RowCounts   Used_MB Unused_MB   Total_MB
1000000     45.20   0.06        45.26
Run Code Online (Sandbox Code Playgroud)

为什么它们的大小相同?稀疏性丢失了吗?

使用过滤索引时的两个查询计划


额外信息

select @@version
Run Code Online (Sandbox Code Playgroud)

Microsoft SQL Server 2017 (RTM-CU16) (KB4508218) - 14.0.3223.3 (X64) 2019 年 7 月 12 日 17:43:08 版权所有 (C) 2017 Microsoft Corporation Developer Edition(64 位),Windows Server 2012 R2 Datacenter 63。 9600:)(管理程序)

在运行查询并仅选择ID字段时,cpu 时间相当,稀疏表的逻辑读取较少。

桌子的大小

SchemaName  TableName   RowCounts   Used_MB Unused_MB   Total_MB
dbo         nonsparse   1002540     89.54   0.10        89.64
dbo         sparse      1002540     27.95   0.20        28.14
Run Code Online (Sandbox Code Playgroud)

强制使用聚集索引或非聚集索引时,cpu 时间差异仍然存在。

Jos*_*ell 7

或者它只是文档中指出的行为?

似乎是这样。文档中提到的“开销”似乎是 CPU 开销。

分析这两个查询,稀疏查询采样了 367 毫秒的 CPU,而非稀疏查询采样了 284 毫秒的 CPU。这是 83 毫秒的差异。

Perfview 的屏幕截图显示了运行查询的线程的总 CPU

大部分在哪里?

两个配置文件看起来非常相似,直到它们到达sqlmin!IndexDataSetSession::GetNextRowValuesInternal. 在这一点上,稀疏代码沿着运行的路径向下移动sqlmin!IndexDataSetSession::GetDataLong,它调用一些看起来与稀疏列特征 ( HasSparseVector, StoreColumnValue)相关的函数,并且加起来为 (42 + 11 =) 53 毫秒。

稀疏列的 CPU 差异截图

为什么它们的大小相同?稀疏性丢失了吗?

是的,当稀疏列用作索引键时,稀疏存储优化似乎不会延续到非聚集索引。因此,无论稀疏性如何,非聚集索引键列都会占用其完整大小,但如果包含的列稀疏且为 NULL,则它们占用的空间为零。

查看DBCC PAGE带有 NULL 值稀疏列的聚集索引页的输出,我可以看到记录长度为 11(ID 为 4 + 7 为标准的每条记录开销):

Record Type = PRIMARY_RECORD        Record Attributes =  NULL_BITMAP    Record Size = 11
Run Code Online (Sandbox Code Playgroud)

对于过滤索引,记录始终为 40,即所有键列大小的总和(4 字节 ID + 20 字节 charval + 4 字节 varcharval + 4 字节 intval + 8 字节 big intval = 40 字节)。

出于某种原因,DBCC PAGE不包括索引记录的“记录大小”中的 7 字节开销:

Record Type = INDEX_RECORD          Record Attributes =  NULL_BITMAP    Record Size = 40
Run Code Online (Sandbox Code Playgroud)

未过滤的索引大小较小(4 字节 ID + 4 字节 intval + 4 字节 varcharval = 12 字节),因为包含两个稀疏列的列,这再次得到了稀疏优化:

Record Type = INDEX_RECORD          Record Attributes =  NULL_BITMAP    Record Size = 12
Run Code Online (Sandbox Code Playgroud)

我想这种行为差异与文档页面中列出的限制之一一致:

稀疏列不能是聚集索引或唯一主键索引的一部分

它们被允许作为非聚集索引中的键,但它们不是存储的,呃,稀疏的。

  • 好一个!再次感谢! (2认同)