Colonnes éparses, temps processeur et index filtrés

10

Épargnant

Lorsque vous effectuez des tests sur des colonnes éparses, comme vous le faites, il y a eu un ralentissement des performances dont je voudrais connaître la cause directe.

DDL

J'ai créé deux tables identiques, une avec 4 colonnes éparses et une sans colonnes éparses.

--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

J'ai ensuite inséré environ 2540 valeurs NON NULL dans les deux.

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;

Ensuite, j'ai inséré des valeurs NULL 1M dans les deux tableaux

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;

Requêtes

Exécution de table sans analyse

Lors de l'exécution de cette requête deux fois sur la table non analysée nouvellement créée:

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

Les lectures logiques montrent 5257 pages

(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.

Et le temps processeur est à 343 ms

 SQL Server Execution Times:
   CPU time = 343 ms,  elapsed time = 3850 ms.

exécution de table clairsemée

Exécution de la même requête deux fois sur la table clairsemée:

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

Les lectures sont plus basses, 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.

Mais le temps processeur est plus élevé, 547 ms .

 SQL Server Execution Times:
   CPU time = 547 ms,  elapsed time = 2406 ms.

Plan d'exécution de table éparse

plan d'exécution de table non éparse


Des questions

Question d'origine

Étant donné que les valeurs NULL ne sont pas stockées directement dans les colonnes clairsemées, l'augmentation du temps processeur pourrait-elle être due au retour des valeurs NULL en tant qu'ensemble de résultats? Ou s'agit-il simplement du comportement indiqué dans la documentation ?

Des colonnes éparses réduisent l'espace requis pour les valeurs nulles au prix de plus de surcharge pour récupérer les valeurs non nulles

Ou les frais généraux sont-ils uniquement liés aux lectures et au stockage utilisés?

Même lors de l'exécution de ssms avec les résultats de suppression après l'option d'exécution, le temps processeur de la sélection clairsemée était plus élevé (407 ms) par rapport à la non clairsemée (219 ms).

ÉDITER

C'était peut-être le surcoût des valeurs non nulles, même s'il n'y en a que 2540, mais je ne suis toujours pas convaincu.

Cela semble être à peu près la même performance, mais le facteur clairsemé a été perdu.

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);

Semble avoir à peu près le même temps d'exécution:

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

 SQL Server Execution Times:
   CPU time = 281 ms,  elapsed time = 319 ms.

Mais pourquoi les lectures logiques ont-elles le même montant maintenant? L'index filtré de la colonne clairsemée ne devrait-il pas stocker quoi que ce soit, à l'exception du champ ID inclus et d'autres pages non liées aux données?

Table 'sparse'. Scan count 1, logical reads 5785,
Table 'nonsparse'. Scan count 1, logical reads 5785

Et la taille des deux indices:

RowCounts   Used_MB Unused_MB   Total_MB
1000000     45.20   0.06        45.26

Pourquoi sont-ils de la même taille? La rareté a-t-elle été perdue?

Les deux plans de requête lors de l'utilisation de l'index filtré


Informaitons supplémentaires

select @@version

Microsoft SQL Server 2017 (RTM-CU16) (KB4508218) - 14.0.3223.3 (X64) 12 juillet 2019 17:43:08 Copyright (C) 2017 Microsoft Corporation Developer Edition (64-bit) sur Windows Server 2012 R2 Datacenter 6.3 (Build 9600:) (Hyperviseur)

Lors de l'exécution des requêtes et de la sélection du champ ID uniquement , le temps processeur est comparable, avec des lectures logiques inférieures pour la table clairsemée.

Taille des tables

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

Lors du forçage de l'index cluster ou non cluster, la différence de temps CPU reste.

Randi Vertongen
la source
1
Pourriez-vous obtenir les plans de la requête post-édition?
George.Palacios
1
@ George.Palacios les a ajoutés :)
Randi Vertongen

Réponses:

6

Ou s'agit-il simplement du comportement indiqué dans la documentation?

Semble si. La «surcharge» mentionnée dans la documentation semble être une surcharge du processeur.

Profilant les deux requêtes, la requête clairsemée a échantillonné 367 ms de CPU, tandis que la non-clairsemée avait 284 ms de CPU. C'est une différence de 83 ms.

capture d'écran de Perfview montrant le CPU total pour le thread qui a exécuté la requête

Où est la majeure partie de cela?

Les deux profils se ressemblent jusqu'à ce qu'ils arrivent sqlmin!IndexDataSetSession::GetNextRowValuesInternal. À ce stade, le code clairsemé descend sur un chemin qui s'exécute sqlmin!IndexDataSetSession::GetDataLong, qui appelle certaines fonctions qui semblent se rapporter à la fonction de colonne clairsemée ( HasSparseVector, StoreColumnValue) et totalisent (42 + 11 =) 53 ms.

capture d'écran de la différence de CPU pour la colonne clairsemée

Pourquoi sont-ils de la même taille? La rareté a-t-elle été perdue?

Oui, il semble que l'optimisation du stockage fragmenté ne se répercute pas sur les index non clusterisés lorsque la colonne fragmentée est utilisée comme clé d'index. Ainsi, les colonnes de clé d'index non cluster prennent leur pleine taille indépendamment de leur parcimonie, mais les colonnes incluses ne prennent aucun espace si elles sont rares et NULL.

En regardant la DBCC PAGEsortie d'une page d'index cluster avec des colonnes éparses à valeur NULL, je peux voir que la longueur d'enregistrement est de 11 (4 pour l'ID + 7 pour la surcharge standard par enregistrement):

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

Pour l'index filtré, l'enregistrement est toujours 40, qui est la somme de la taille de toutes les colonnes clés (ID 4 octets + 20 octets charval + 4 octets varcharval + 4 octets intval + 8 octets grand intval = 40 octets).

Pour une raison quelconque, DBCC PAGEn'inclut pas la surcharge de 7 octets dans "Taille d'enregistrement" pour les enregistrements d'index:

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

La taille de l'index non filtré est plus petite (4 octets ID + 4 octets intval + 4 octets varcharval = 12 octets) car deux des colonnes éparses sont des colonnes incluses, ce qui obtient à nouveau l'optimisation de rareté:

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

Je suppose que cette différence de comportement correspond à l'une des limitations répertoriées dans la page des documents:

Une colonne fragmentée ne peut pas faire partie d'un index cluster ou d'un index de clé primaire unique

Ils sont autorisés à être des clés dans des index non clusterisés, mais ils ne sont pas stockés, euh, de façon clairsemée.

Josh Darnell
la source
2
Joli! Merci encore!
Randi Vertongen du