Colunas esparsas, tempo da CPU e índices filtrados

10

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.

Randi Vertongen
fonte
11
Você poderia obter os planos para a consulta pós-edição?
George.Palacios
11
@ George.Palacios adicionou-los :)
Randi Vertongen

Respostas:

6

Ou é simplesmente o comportamento conforme observado na documentação?

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.

captura de tela do Perfview mostrando a CPU total para o segmento que executou a consulta

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 é executado sqlmin!IndexDataSetSession::GetDataLong, que chama algumas funções que parecem relacionadas ao recurso de coluna esparsa ( HasSparseVector, StoreColumnValue) e somam (42 + 11 =) 53 ms.

captura de tela da diferença de CPU para coluna esparsa

Por que esses são do mesmo tamanho? A escassez foi perdida?

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 PAGEsaí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):

Record Type = PRIMARY_RECORD        Record Attributes =  NULL_BITMAP    Record Size = 11

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 PAGEnão inclui a sobrecarga de 7 bytes em "Tamanho do Registro" para registros de índice:

Record Type = INDEX_RECORD          Record Attributes =  NULL_BITMAP    Record Size = 40

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:

Record Type = INDEX_RECORD          Record Attributes =  NULL_BITMAP    Record Size = 12

Acho que essa diferença de comportamento está alinhada com uma das limitações listadas na página de documentos:

Uma coluna esparsa não pode fazer parte de um índice em cluster ou de um índice de chave primária exclusivo

Eles podem ser chaves em índices não clusterizados, mas não são armazenados esparsamente.

Josh Darnell
fonte
2
Agradável! Obrigado novamente!
Randi Vertongen