Разреженные столбцы, время процессора и отфильтрованные индексы

10

Sparsing

При выполнении некоторых тестов для разреженных столбцов, как и у вас, произошел спад производительности, который я хотел бы узнать по прямой причине.

DDL

Я создал две идентичные таблицы, одну с 4 разреженными столбцами и одну без разреженных столбцов.

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

Затем я вставил около 2540 значений NULL в оба.

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;

После этого я вставил 1M NULL значения в обе таблицы

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;

Запросы

Неполное выполнение таблицы

При выполнении этого запроса дважды для вновь созданной непрореженной таблицы:

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

Логические чтения показывают 5257 страниц

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

И время процессора составляет 343 мс

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

разреженное выполнение таблицы

Выполнение одного и того же запроса дважды для разреженной таблицы:

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

Показания ниже, 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.

Но время процессора выше, 547 мс .

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

План выполнения разреженных таблиц

план разбора не разреженных таблиц


Вопросов

Оригинальный вопрос

Поскольку значения NULL не сохраняются непосредственно в разреженных столбцах, может ли увеличение времени процессора быть связано с возвратом значений NULL в качестве набора результатов? Или это просто поведение, указанное в документации ?

Разреженные столбцы уменьшают требования к пространству для нулевых значений за счет дополнительных затрат на извлечение ненулевых значений

Или накладные расходы связаны только с чтением и хранением?

Даже при запуске ssms с опциями отмены после выполнения время процессора разреженного выбора было выше (407 мс) по сравнению с непрореженным (219 мс).

РЕДАКТИРОВАТЬ

Возможно, это были издержки ненулевых значений, даже если присутствуют только 2540, но я все еще не убежден.

Кажется, это примерно одинаковая производительность, но разреженный фактор был потерян.

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

Кажется, примерно одинаковое время выполнения:

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

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

Но почему логические чтения сейчас одинаковы? Разве в отфильтрованном индексе для разреженного столбца не должно храниться ничего, кроме включенного поля идентификатора и некоторых других страниц без данных?

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

И размер обоих показателей:

RowCounts   Used_MB Unused_MB   Total_MB
1000000     45.20   0.06        45.26

Почему они одинакового размера? Была ли потеряна редкость?

Оба плана запроса при использовании отфильтрованного индекса


Дополнительная информация

select @@version

Microsoft SQL Server 2017 (RTM-CU16) (KB4508218) - 14.0.3223.3 (X64) 12 июля 2019 г. 17:43:08 Авторское право (C) 2017 Выпуск Microsoft Corporation для разработчиков (64-разрядная версия) в Windows Server 2012 R2 Datacenter 6.3 (сборка) 9600 :) (гипервизор)

При выполнении запросов и выборе только поля идентификатора время процессора сравнимо с меньшим логическим чтением для разреженной таблицы.

Размер столов

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

При принудительном использовании кластеризованного или некластеризованного индекса разница во времени процессора сохраняется.

Рэнди Вертонген
источник
1
Не могли бы вы получить планы для запроса после редактирования?
Джордж. Паласиос
1
@ George.Palacios добавил их :)
Рэнди Вертонген

Ответы:

6

Или это просто поведение, указанное в документации?

Кажется, так. Упомянутые в документации «накладные расходы», по-видимому, являются накладными расходами ЦП.

Профилируя два запроса, разреженный запрос выбрал 367 мс ЦП, в то время как у не разреженного было 284 мс ЦП. Это разница 83 мс.

снимок экрана с Perfview, показывающий суммарный процессор для потока, который выполнил запрос

Где это больше всего?

Оба профиля выглядят очень похожими, пока не дойдут до sqlmin!IndexDataSetSession::GetNextRowValuesInternal. В этот момент разреженный код идет по пути, который выполняется sqlmin!IndexDataSetSession::GetDataLong, который вызывает некоторые функции, которые выглядят так, как будто они связаны с функцией разреженного столбца ( HasSparseVector, StoreColumnValue), и в сумме составляют (42 + 11 =) 53 мс.

снимок экрана разницы в процессоре для разреженного столбца

Почему они одинакового размера? Была ли потеряна редкость?

Да, похоже, что оптимизация разреженного хранилища не переносится на некластеризованные индексы, когда разреженный столбец используется в качестве ключа индекса. Таким образом, столбцы ключа кластеризованного индекса занимают свой полный размер независимо от разреженности, но включенные столбцы занимают нулевое пространство, если они разрежены и имеют значение NULL.

Глядя на DBCC PAGEвыходные данные страницы кластерного индекса с разреженными столбцами, имеющими значение NULL, я вижу, что длина записи равна 11 (4 для идентификатора + 7 для стандартных накладных расходов на запись):

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

Для отфильтрованного индекса запись всегда равна 40, что является суммой размера всех ключевых столбцов (4-байтовый идентификатор + 20-байтовый charval + 4-байтовый varcharval + 4-байтовый intval + 8-байтовый большой intval = 40 байт).

По некоторым причинам DBCC PAGEне включает 7-байтовые издержки в «Размер записи» для записей индекса:

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

Размер нефильтрованного индекса меньше (4 байта ID + 4 байта intval + 4 байта varcharval = 12 байтов), поскольку два разреженных столбца включают столбцы, что опять-таки позволяет оптимизировать разреженность:

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

Я предполагаю, что эта разница в поведении соответствует одному из ограничений, перечисленных на странице документации:

Разреженный столбец не может быть частью кластерного индекса или индекса уникального первичного ключа

Им разрешено быть ключами в некластеризованных индексах, но они не хранятся редко.

Джош Дарнелл
источник
2
Хороший! Еще раз спасибо!
Рэнди Вертонген