Rzadkie kolumny, czas procesora i przefiltrowane indeksy

10

Sparowanie

Podczas przeprowadzania niektórych testów na rzadkich kolumnach, tak jak Ty, wystąpił spadek wydajności, który chciałbym poznać bezpośrednią przyczynę.

DDL

Utworzyłem dwie identyczne tabele, jedną z 4 rzadkimi kolumnami i jedną bez rzadkich kolumn.

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

Następnie wstawiłem około 2540 wartości NON-NULL do obu.

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;

Następnie wstawiłem wartości 1M NULL do obu tabel

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;

Zapytania

Niesprawne wykonanie tabeli

W przypadku dwukrotnego uruchomienia tego zapytania w nowo utworzonej nieskomplikowanej tabeli:

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

Logiczne odczyty pokazują 5257 stron

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

Czas procesora wynosi 343 ms

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

rzadkie wykonanie tabeli

Dwukrotne uruchomienie tego samego zapytania w rzadkiej tabeli:

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

Odczyty są niższe, 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.

Ale czas procesora jest dłuższy, 547 ms .

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

Rzadki plan wykonania tabeli

nieskomplikowany plan wykonania tabeli


pytania

Oryginalne pytanie

Ponieważ wartości NULL nie są zapisywane bezpośrednio w rzadkich kolumnach, czy wzrost czasu procesora może wynikać ze zwrotu wartości NULL jako zestawu wyników? Czy jest to po prostu zachowanie wskazane w dokumentacji ?

Rzadkie kolumny zmniejszają wymagania dotyczące miejsca dla wartości zerowych kosztem większego narzutu w celu pobrania wartości niepustych

A może narzut związany jest tylko z odczytami i pamięcią?

Nawet przy uruchamianiu ssms z wynikami odrzucania po wykonaniu polecenia czas procesora dla rzadkiego wyboru był wyższy (407 ms) w porównaniu do nieskomplikowanego (219 ms).

EDYTOWAĆ

Mogłoby to być narzut niepustych wartości, nawet jeśli obecnych jest tylko 2540, ale wciąż nie jestem przekonany.

Wydaje się, że dotyczy to tej samej wydajności, ale czynnik rzadki został utracony.

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

Wydaje się mieć mniej więcej ten sam czas wykonania:

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

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

Ale dlaczego logiczne odczyty są teraz takie same? Czy filtrowany indeks dla rzadkiej kolumny nie powinien przechowywać niczego oprócz dołączonego pola identyfikatora i niektórych innych stron, które nie są danymi?

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

A wielkość obu wskaźników:

RowCounts   Used_MB Unused_MB   Total_MB
1000000     45.20   0.06        45.26

Dlaczego są tego samego rozmiaru? Czy utracono rzadkość?

Oba plany zapytań przy użyciu filtrowanego indeksu


Informacje dodatkowe

select @@version

Microsoft SQL Server 2017 (RTM-CU16) (KB4508218) - 14.0.3223.3 (X64) 12 lipca 2019 17:43:08 Copyright (C) 2017 Microsoft Corporation Developer Edition (64-bitowy) w systemie Windows Server 2012 R2 Datacenter 6.3 (kompilacja 9600:) (Hypervisor)

Podczas uruchamiania zapytań i tylko wybrania pola ID czas procesora jest porównywalny, z niższymi odczytami logicznymi dla rzadkiej tabeli.

Rozmiar stołów

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

Podczas wymuszania indeksu klastrowanego lub nieklastrowanego różnica czasu procesora pozostaje.

Randi Vertongen
źródło
1
Czy możesz uzyskać plany dotyczące zapytania po edycji?
George.Palacios,
1
@ George.Palacios dodał je :)
Randi Vertongen

Odpowiedzi:

6

Czy jest to po prostu zachowanie wskazane w dokumentacji?

Na to wygląda. „Narzut” wymieniony w dokumentacji wydaje się być narzutem procesora.

Profilując dwa zapytania, rzadkie zapytanie próbkowało 367 ms procesora, podczas gdy nie rzadkie miało 284 ms procesora. To różnica 83 ms.

zrzut ekranu z Perfview pokazujący całkowity procesor dla wątku, który uruchomił zapytanie

Gdzie jest tego większość?

Oba profile wyglądają bardzo podobnie, dopóki się nie znajdą sqlmin!IndexDataSetSession::GetNextRowValuesInternal. W tym momencie rzadki kod przechodzi w dół ścieżki, która uruchamia sqlmin!IndexDataSetSession::GetDataLongniektóre funkcje, które wyglądają tak, jakby odnosiły się do funkcji rzadkiej kolumny ( HasSparseVector, StoreColumnValue) i sumują się do (42 + 11 =) 53 ms.

zrzut ekranu różnicy procesora dla rzadkiej kolumny

Dlaczego są tego samego rozmiaru? Czy utracono rzadkość?

Tak, wygląda na to, że oszczędna optymalizacja pamięci nie przenosi się do indeksów nieklastrowanych, gdy rzadka kolumna jest używana jako klucz indeksu. Tak więc nieklastrowe kolumny kluczy indeksu zajmują pełny rozmiar niezależnie od rzadkości, ale uwzględnione kolumny zajmują zero miejsca, jeśli są rzadkie i mają wartość NULL.

Patrząc na DBCC PAGEwyniki z klastrowanej strony indeksu z rzadkimi kolumnami o wartości NULL, widzę, że długość rekordu wynosi 11 (4 dla ID + 7 dla standardowego narzutu na rekord):

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

W przypadku filtrowanego indeksu rekord wynosi zawsze 40, co jest sumą wielkości wszystkich kluczowych kolumn (4 bajty ID + 20 bajtów charval + 4 bajty varcharval + 4 bajty intval + 8 bajtów big intval = 40 bajtów).

Z jakiegoś powodu DBCC PAGEnie uwzględnia 7 bajtów narzutu w „Record Record” dla rekordów indeksu:

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

Niefiltrowany rozmiar indeksu jest mniejszy (4 bajty ID + 4 bajty intval + 4 bajty varcharval = 12 bajtów), ponieważ dwie rzadkie kolumny są uwzględnione kolumny, co ponownie uzyskuje optymalizację rzadkości:

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

Wydaje mi się, że ta różnica w zachowaniu pokrywa się z jednym z ograniczeń wymienionych na stronie dokumentacji:

Rzadka kolumna nie może być częścią indeksu klastrowego ani unikalnego indeksu klucza podstawowego

Mogą być kluczami w indeksach nieklastrowanych, ale nie są przechowywane, rzadko.

Josh Darnell
źródło
2
Niezłe! Dzięki jeszcze raz!
Randi Vertongen,