Sparsing
Ao fazer alguns testes em colunas esparsas, como você faz, houve um revés de desempenho do qual gostaria de saber a causa direta.
DDL
Criei duas tabelas idênticas, uma com 4 colunas esparsas e outra sem colunas esparsas.
--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
Em seguida, inseri cerca de 2540 valores NON-NULL em ambos.
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;
Posteriormente, inseri valores NULL de 1 milhão nas duas tabelas
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;
Consultas
Execução de tabela não separada
Ao executar esta consulta duas vezes na tabela não-separada recém-criada:
SET STATISTICS IO, TIME ON;
SELECT * FROM dbo.nonsparse
WHERE 1= (SELECT 1) -- force non trivial plan
OPTION(RECOMPILE,MAXDOP 1);
As leituras lógicas mostram 5257 páginas
(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.
E o tempo da CPU é de 343 ms
SQL Server Execution Times:
CPU time = 343 ms, elapsed time = 3850 ms.
execução de tabela esparsa
Executando a mesma consulta duas vezes na tabela esparsa:
SELECT * FROM dbo.sparse
WHERE 1= (SELECT 1) -- force non trivial plan
OPTION(RECOMPILE,MAXDOP 1);
As leituras são mais baixas, 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.
Mas o tempo da CPU é maior, 547 ms .
SQL Server Execution Times:
CPU time = 547 ms, elapsed time = 2406 ms.
Plano de execução de tabela esparsa
plano de execução de tabela não esparsa
Questões
Pergunta original
Como os valores NULL não são armazenados diretamente nas colunas esparsas, o aumento no tempo da CPU deve-se ao retorno dos valores NULL como um conjunto de resultados? Ou é simplesmente o comportamento conforme observado na documentação ?
Colunas esparsas reduzem os requisitos de espaço para valores nulos ao custo de mais despesas gerais para recuperar valores não nulos
Ou a sobrecarga está relacionada apenas às leituras e armazenamento usados?
Mesmo ao executar ssms com os resultados de descarte após a opção de execução, o tempo de CPU da seleção esparsa era mais alto (407 ms) em comparação com o não esparso (219 ms).
EDITAR
Pode ter sido a sobrecarga dos valores não nulos, mesmo se houver apenas 2540 presentes, mas ainda não estou convencido.
Parece ser o mesmo desempenho, mas o fator esparso foi perdido.
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);
Parece ter aproximadamente o mesmo tempo de execução:
SQL Server Execution Times:
CPU time = 297 ms, elapsed time = 292 ms.
SQL Server Execution Times:
CPU time = 281 ms, elapsed time = 319 ms.
Mas por que as leituras lógicas têm a mesma quantidade agora? O índice filtrado da coluna esparsa não deve armazenar nada, exceto o campo de ID incluído e algumas outras páginas que não são de dados?
Table 'sparse'. Scan count 1, logical reads 5785,
Table 'nonsparse'. Scan count 1, logical reads 5785
E o tamanho dos dois índices:
RowCounts Used_MB Unused_MB Total_MB
1000000 45.20 0.06 45.26
Por que esses são do mesmo tamanho? A escassez foi perdida?
Ambos os planos de consulta ao usar o índice filtrado
Informação extra
select @@version
Microsoft SQL Server 2017 (RTM-CU16) (KB4508218) - 14.0.3223.3 (X64) 12 de julho de 2019 às 17:43:08 Copyright (C) 2017 Microsoft Corporation Developer Edition (64 bits) no Windows Server 2012 R2 Datacenter 6.3 (Compilação 9600:) (Hypervisor)
Ao executar as consultas e selecionar apenas o campo ID , o tempo da CPU é comparável, com leituras lógicas mais baixas para a tabela esparsa.
Tamanho das tabelas
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
Ao forçar o índice clusterizado ou não clusterizado, a diferença de tempo da CPU permanece.
fonte
Respostas:
Parece que sim. A "sobrecarga" mencionada na documentação parece ser uma sobrecarga da CPU.
No perfil das duas consultas, a consulta esparsa amostrou 367 ms da CPU, enquanto a não esparsa teve 284 ms da CPU. Essa é uma diferença de 83 ms.
Onde está a maior parte disso?
Ambos os perfis parecem muito semelhantes até chegarem
sqlmin!IndexDataSetSession::GetNextRowValuesInternal
. Nesse ponto, o código esparso segue um caminho que é executadosqlmin!IndexDataSetSession::GetDataLong
, que chama algumas funções que parecem relacionadas ao recurso de coluna esparsa (HasSparseVector
,StoreColumnValue
) e somam (42 + 11 =) 53 ms.Sim, parece que a otimização de armazenamento esparso não é transferida para índices não clusterizados quando a coluna esparsa é usada como uma chave de índice. Portanto, as colunas de chave de índice não clusterizadas ocupam seu tamanho total, independentemente da escassez, mas as colunas incluídas ocupam zero espaço se forem esparsas e NULL.
Observando a
DBCC PAGE
saída de uma página de índice em cluster com colunas esparsas com valor NULL, posso ver que o comprimento do registro é 11 (4 para o ID + 7 para a sobrecarga padrão por registro):Para o índice filtrado, o registro é sempre 40, que é a soma do tamanho de todas as colunas-chave (ID de 4 bytes + charval de 20 bytes + varvalval de 4 bytes + intcharchar de 4 bytes + intval de 4 bytes + intval grande de 8 bytes = 40 bytes).
Por algum motivo,
DBCC PAGE
não inclui a sobrecarga de 7 bytes em "Tamanho do Registro" para registros de índice:O tamanho do índice não filtrado é menor (ID de 4 bytes + intval de 4 bytes + varcharval de 4 bytes = 12 bytes) porque duas das colunas esparsas são colunas incluídas, o que novamente obtém a otimização da escassez:
Acho que essa diferença de comportamento está alinhada com uma das limitações listadas na página de documentos:
Eles podem ser chaves em índices não clusterizados, mas não são armazenados esparsamente.
fonte