Sparse Spalten, CPU-Zeit und gefilterte Indizes


10

Sparsing

Bei einigen Tests mit spärlichen Spalten gab es wie bei Ihnen einen Leistungsabfall, dessen direkte Ursache ich gerne kennen würde.

DDL

Ich habe zwei identische Tabellen erstellt, eine mit 4 spärlichen Spalten und eine ohne spärliche Spalten.

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

Ich habe dann ungefähr 2540 NON-NULL- Werte in beide eingefügt .

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;

Danach habe ich 1M NULL- Werte in beide Tabellen eingefügt

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;

Abfragen

Nicht sparsame Tabellenausführung

Wenn Sie diese Abfrage zweimal für die neu erstellte nicht sparsame Tabelle ausführen:

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

Die logischen Lesevorgänge zeigen 5257 Seiten

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

Und die CPU-Zeit liegt bei 343 ms

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

spärliche Tabellenausführung

Ausführen derselben Abfrage zweimal in der Tabelle mit geringer Dichte:

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

Die Lesungen sind niedriger, 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.

Die CPU-Zeit ist jedoch mit 547 ms höher .

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

Sparse Table Execution Plan

Ausführungsplan für nicht spärliche Tabellen


Fragen

Ursprüngliche Frage

Könnte die Erhöhung der CPU-Zeit auf die Rückgabe der NULL- Werte als Ergebnismenge zurückzuführen sein, da die NULL- Werte nicht direkt in den spärlichen Spalten gespeichert werden ? Oder ist es einfach das in der Dokumentation angegebene Verhalten ?

Durch spärliche Spalten wird der Platzbedarf für Nullwerte auf Kosten eines höheren Overheads zum Abrufen von Nicht-Null-Werten reduziert

Oder bezieht sich der Overhead nur auf Lese- und Speicherbedarf?

Selbst wenn ssms mit den Verwerfungsergebnissen nach der Ausführungsoption ausgeführt wird, war die CPU-Zeit der Sparse-Auswahl höher (407 ms) als die nicht-Sparse-Auswahl (219 ms).

BEARBEITEN

Es könnte der Overhead der Nicht-Null-Werte gewesen sein, selbst wenn nur 2540 vorhanden sind, aber ich bin immer noch nicht überzeugt.

Dies scheint ungefähr die gleiche Leistung zu sein, aber der spärliche Faktor ging verloren.

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

Scheint ungefähr die gleiche Ausführungszeit zu haben:

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

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

Aber warum sind die logischen Lesevorgänge jetzt gleich hoch? Sollte der gefilterte Index für die Spalte mit geringer Dichte nicht etwas anderes als das enthaltene ID-Feld und einige andere Nicht-Datenseiten speichern?

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

Und die Größe beider Indizes:

RowCounts   Used_MB Unused_MB   Total_MB
1000000     45.20   0.06        45.26

Warum sind diese gleich groß? War die Spärlichkeit verloren?

Beide Abfragepläne bei Verwendung des gefilterten Index


Zusatzinformation

select @@version

Microsoft SQL Server 2017 (RTM-CU16) (KB4508218) - 14.0.3223.3 (X64) 12. Juli 2019 17:43:08 Copyright (C) 2017 Microsoft Corporation Developer Edition (64-Bit) unter Windows Server 2012 R2 Datacenter 6.3 (Build) 9600 :) (Hypervisor)

Während Sie die Abfragen ausführen und nur das ID- Feld auswählen , ist die CPU-Zeit vergleichbar, mit niedrigeren logischen Lesevorgängen für die Sparse-Tabelle.

Größe der Tabellen

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

Beim Erzwingen des Clustered- oder Nonclustered-Index bleibt die CPU-Zeitdifferenz bestehen.


1
Könnten Sie die Pläne für die Abfrage nach der Bearbeitung erhalten?
George.Palacios

1
@ George.Palacios fügte sie hinzu :)
Randi Vertongen

Antworten:


6

Oder ist es einfach das in der Dokumentation angegebene Verhalten?

Scheint so. Der in der Dokumentation erwähnte "Overhead" scheint CPU-Overhead zu sein.

Bei der Profilerstellung der beiden Abfragen wurden 367 ms CPU abgetastet, während die nicht spärliche Abfrage 284 ms CPU hatte. Das ist ein Unterschied von 83 ms.

Screenshot von Perfview mit der Gesamt-CPU für den Thread, in dem die Abfrage ausgeführt wurde

Wo ist das meiste davon?

Beide Profile sehen sich sehr ähnlich, bis sie dazu kommen sqlmin!IndexDataSetSession::GetNextRowValuesInternal. Zu diesem Zeitpunkt durchläuft der Sparse-Code einen Pfad sqlmin!IndexDataSetSession::GetDataLong, der ausgeführt wird. Dabei werden einige Funktionen aufgerufen, die so aussehen, als würden sie sich auf das Sparse-Spalten-Feature ( HasSparseVector, StoreColumnValue) beziehen , und es werden (42 + 11 =) 53 ms addiert.

Screenshot der CPU-Differenz für die Spalte mit geringer Dichte

Warum sind diese gleich groß? War die Spärlichkeit verloren?

Ja, es scheint, dass die Optimierung des Sparse-Speichers nicht auf nicht gruppierte Indizes übertragen wird, wenn die Sparse-Spalte als Indexschlüssel verwendet wird. Nicht gruppierte Indexschlüsselspalten nehmen unabhängig von der Spärlichkeit ihre volle Größe ein, eingeschlossene Spalten belegen jedoch keinen Speicherplatz, wenn sie spärlich und NULL sind.

Wenn DBCC PAGEich mir die Ausgabe einer gruppierten Indexseite mit spärlichen Spalten mit NULL-Werten ansehe, sehe ich, dass die Datensatzlänge 11 beträgt (4 für die ID + 7 für den Standard-Overhead pro Datensatz):

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

Für den gefilterten Index ist der Datensatz immer 40, was die Summe der Größe aller Schlüsselspalten ist (4-Byte-ID + 20-Byte-Charval + 4-Byte-Varcharval + 4-Byte-Intval + 8-Byte-Big-Intval = 40 Byte).

Enthält aus irgendeinem Grund DBCC PAGEnicht den 7-Byte-Overhead in "Datensatzgröße" für Indexdatensätze:

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

Die nicht gefilterte Indexgröße ist kleiner (4-Byte-ID + 4-Byte-Intval + 4-Byte-Varcharval = 12 Byte), da zwei der spärlichen Spalten enthaltene Spalten sind, wodurch wiederum die Spärlichkeitsoptimierung erzielt wird:

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

Ich denke, dieser Unterschied im Verhalten stimmt mit einer der Einschränkungen überein, die auf der Dokumentseite aufgeführt sind:

Eine Spalte mit geringer Dichte kann nicht Teil eines Clustered-Index oder eines eindeutigen Primärschlüsselindex sein

Sie dürfen Schlüssel in nicht gruppierten Indizes sein, aber sie werden nicht sparsam gespeichert.


2
Schön! Danke noch einmal!
Randi Vertongen
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.