稀疏
在对稀疏列进行一些测试时,正如您所做的那样,我想知道直接原因是性能受挫。
DDL
我创建了两张相同的表,一张有 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);
DML
然后我在两者中插入了大约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;
之后,我在两个表中都插入了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;
查询
非稀疏表执行
在新创建的非稀疏表上运行此查询两次时:
SET STATISTICS IO, TIME ON;
SELECT * FROM dbo.nonsparse
WHERE 1= (SELECT 1) -- force non trivial plan
OPTION(RECOMPILE,MAXDOP 1);
逻辑读取显示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.
cpu时间为343 ms
SQL Server Execution Times:
CPU time = 343 ms, elapsed time = 3850 ms.
稀疏表执行
在稀疏表上运行相同的查询两次:
SELECT * FROM dbo.sparse
WHERE 1= (SELECT 1) -- force non trivial plan
OPTION(RECOMPILE,MAXDOP 1);
读数较低,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.
但是 cpu 时间更高,547 ms。
SQL Server Execution Times:
CPU time = 547 ms, elapsed time = 2406 ms.
问题
原始问题
由于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);
似乎有大约相同的执行时间:
SQL Server Execution Times:
CPU time = 297 ms, elapsed time = 292 ms.
SQL Server Execution Times:
CPU time = 281 ms, elapsed time = 319 ms.
但是为什么现在逻辑读取量相同?除了包含的 ID 字段和其他一些非数据页面之外,稀疏列的过滤索引不应该不存储任何内容吗?
Table 'sparse'. Scan count 1, logical reads 5785,
Table 'nonsparse'. Scan count 1, logical reads 5785
以及两个指数的大小:
RowCounts Used_MB Unused_MB Total_MB
1000000 45.20 0.06 45.26
为什么这些大小一样?稀疏性消失了吗?
额外信息
select @@version
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-bit) on Windows Server 2012 R2 Datacenter 6.3 (Build 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
当强制使用聚集索引或非聚集索引时,cpu 时间差仍然存在。
似乎是这样。文档中提到的“开销”似乎是 CPU 开销。
分析这两个查询,稀疏查询采样了 367 毫秒的 CPU,而非稀疏查询有 284 毫秒的 CPU。这是 83 毫秒的差异。
大部分在哪里?
两个配置文件看起来非常相似,直到它们到达
sqlmin!IndexDataSetSession::GetNextRowValuesInternal
. 此时,稀疏代码沿着运行的路径向下运行sqlmin!IndexDataSetSession::GetDataLong
,该路径调用了一些看起来与稀疏列特征 (HasSparseVector
,StoreColumnValue
) 相关的函数,并且加起来为 (42 + 11 =) 53 毫秒。是的,当稀疏列用作索引键时,稀疏存储优化似乎不会延续到非聚集索引。因此,非聚集索引键列无论是否稀疏都会占用其完整大小,但如果包含的列稀疏且为 NULL,则包含的列占用零空间。
查看
DBCC PAGE
具有 NULL 值稀疏列的聚集索引页的输出,我可以看到记录长度为 11(ID 为 4,标准每记录开销为 7):对于过滤后的索引,记录始终为 40,即所有键列的大小之和(4 字节 ID + 20 字节 charval + 4 字节 varcharval + 4 字节 intval + 8 字节 big intval = 40 字节)。
出于某种原因,
DBCC PAGE
不包括索引记录的“记录大小”中的 7 字节开销:未过滤的索引大小更小(4 字节 ID + 4 字节 intval + 4 字节 varcharval = 12 字节),因为其中两个稀疏列是包含列,这再次获得了稀疏优化:
我猜这种行为差异符合文档页面中列出的限制之一:
它们被允许作为非聚集索引中的键,但它们不会被存储,呃,稀疏的。