Странное поведение с размерами выборки для обновлений статистики

25

Я изучал пороговые значения выборки с помощью обновлений статистики на SQL Server (2012) и заметил некоторое любопытное поведение. По сути, число выбранных строк при некоторых обстоятельствах может меняться - даже с одним и тем же набором данных.

Я запускаю этот запрос:

--Drop table if exists
IF (OBJECT_ID('dbo.Test')) IS NOT NULL DROP TABLE dbo.Test;

--Create Table for Testing
CREATE TABLE dbo.Test(Id INT IDENTITY(1,1) CONSTRAINT PK_Test PRIMARY KEY CLUSTERED, TextValue VARCHAR(20) NULL);

--Insert enough data so we have more than 8Mb (the threshold at which sampling kicks in)
INSERT INTO dbo.Test(TextValue) 
SELECT TOP 1000000 'blahblahblah'
FROM sys.objects a, sys.objects b, sys.objects c, sys.objects d;  

--Create Index on TextValue
CREATE INDEX IX_Test_TextValue ON dbo.Test(TextValue);

--Update Statistics without specifying how many rows to sample
UPDATE STATISTICS dbo.Test IX_Test_TextValue;

--View the Statistics
DBCC SHOW_STATISTICS('dbo.Test', IX_Test_TextValue) WITH STAT_HEADER;

Когда я смотрю на вывод SHOW_STATISTICS, я обнаруживаю, что «Выборка строк» ​​меняется с каждым полным выполнением (т. Е. Таблица удаляется, воссоздается и заполняется).

Например:

Ряды, выбранные

  • 318618
  • 319240
  • 324198
  • 314154

Я ожидал, что эта цифра будет одинаковой каждый раз, когда таблица идентична. Кстати, я не получаю такого поведения, если я просто удаляю данные и вставляю их заново.

Это не критический вопрос, но мне было бы интересно понять, что происходит.

Мэтью МакДжиффен
источник
2
Сколько файлов в файловой группе вы вставляете? Я пробовал пару раз в 2016 году, и оба раза таблица разбивалась на 3584 страницы с 279 строками и 1 с 64. Два разных размера выборки, которые я видел, были 314712 и 315270 - оба с точностью до 279.
Martin Smith
1
@JoeObbish - он всегда читает целые страницы AFAIK, поэтому меня это не удивило. По какой-то причине я подумал, что числа в вопросе не соответствуют этому шаблону, хотя. Но переделав математику, которую они делают. 318618 = 1142*279, 319240 = 1144*279 + 64, 324198=1162*279И 314154=1126поэтому дисперсия является число страниц , отобранных.
Мартин Смит
@MartinSmithПросто один файл - цифра 279 интересна, мне всегда нравится понимать паттерны, связанные с этим
Мэтью МакГиффен

Ответы:

26

Задний план

Данные для объекта статистики собираются с помощью оператора в форме:

SELECT 
    StatMan([SC0], [SC1], [SB0000]) 
FROM 
(
    SELECT TOP 100 PERCENT 
        [SC0], [SC1], STEP_DIRECTION([SC0]) OVER (ORDER BY NULL) AS [SB0000]
    FROM 
    (
        SELECT 
            [TextValue] AS [SC0], 
            [Id] AS [SC1] 
        FROM [dbo].[Test] 
            TABLESAMPLE SYSTEM (2.223684e+001 PERCENT) 
            WITH (READUNCOMMITTED) 
    ) AS _MS_UPDSTATS_TBL_HELPER 
    ORDER BY 
        [SC0], 
        [SC1], 
        [SB0000] 
) AS _MS_UPDSTATS_TBL
OPTION (MAXDOP 1)

Вы можете собрать этот оператор с помощью расширенных событий или Profiler ( SP:StmtCompleted).

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

Количество выбранных строк зависит от количества целых страниц, выбранных для выборки. Каждая страница таблицы либо выбрана, либо нет. Все строки на выбранных страницах вносят свой вклад в статистику.

Случайные числа

SQL Server использует генератор случайных чисел, чтобы решить, соответствует ли страница требованиям или нет. В этом случае используется генератор случайных чисел Лемера со значениями параметров, как показано ниже:

X следующий = X seed * 7 5 мод (2 31 - 1)

Значение рассчитывается как сумма:Xseed

  • Младшая целая часть bigintбазовой таблицы ( ), partition_idнапример

    SELECT
        P.[partition_id] & 0xFFFFFFFF
    FROM sys.partitions AS P
    WHERE
        P.[object_id] = OBJECT_ID(N'dbo.Test', N'U')
        AND P.index_id = 1;
    
  • Значение, указанное в REPEATABLEпункте

    • Для пробы UPDATE STATISTICS, то REPEATABLEзначение равно 1.
    • Это значение предоставляется в m_randomSeedэлементе внутренней информации отладки метода доступа, показанной в планах выполнения, например, когда включен флаг трассировки 8666<Field FieldName="m_randomSeed" FieldValue="1" />

Для SQL Server 2012 этот расчет происходит в sqlmin!UnOrderPageScanner::StartScan:

mov     edx,dword ptr [rcx+30h]
add     edx,dword ptr [rcx+2Ch]

где memory at [rcx+30h]содержит младшие 32 бита идентификатора раздела, а memory at [rcx+2Ch]содержит используемое REPEATABLEзначение.

Генератор случайных чисел инициализируется позже в том же методе, вызывая sqlmin!RandomNumGenerator::Init, где инструкция:

imul    r9d,r9d,41A7h

... умножает начальное число на 41A7шестнадцатеричное (16807 десятичное = 7 5 ), как показано в приведенном выше уравнении.

Более поздние случайные числа (для отдельных страниц) генерируются с использованием того же основного кода, встроенного в sqlmin!UnOrderPageScanner::SetupSubScanner.

Statman

Для примера StatManзапроса, показанного выше, будут собраны те же страницы, что и для оператора T-SQL:

SELECT 
    COUNT_BIG(*) 
FROM dbo.Test AS T 
    TABLESAMPLE SYSTEM (2.223684e+001 PERCENT)  -- Same sample %
    REPEATABLE (1)                              -- Always 1 for statman
    WITH (INDEX(0));                            -- Scan base object

Это будет соответствовать выводу:

SELECT 
    DDSP.rows_sampled
FROM sys.stats AS S
CROSS APPLY sys.dm_db_stats_properties(S.[object_id], S.stats_id) AS DDSP
WHERE 
    S.[object_id] = OBJECT_ID(N'dbo.Test', N'U')
    AND S.[name] = N'IX_Test_TextValue';

Край случае

Одним из следствий использования генератора случайных чисел MINSTD Lehmer является то, что начальные значения нуля и int.max не должны использоваться, так как это приведет к тому, что алгоритм выдаст последовательность нулей (выбор каждой страницы).

Код обнаруживает ноль и использует значение из системных «часов» в качестве начального числа в этом случае. Это не делает то же самое, если начальное число int.max ( 0x7FFFFFFF= 2 31 - 1).

Мы можем разработать этот сценарий, поскольку начальное начальное число рассчитывается как сумма младших 32 бит идентификатора раздела и REPEATABLEзначения. REPEATABLEЗначение , которое приведет к семени быть int.max и , следовательно , каждая страница выбирается для выборки:

SELECT
    0x7FFFFFFF - (P.[partition_id] & 0xFFFFFFFF)
FROM sys.partitions AS P
WHERE
    P.[object_id] = OBJECT_ID(N'dbo.Test', N'U')
    AND P.index_id = 1;

Работая это в полный пример:

DECLARE @SQL nvarchar(4000) = 
    N'
    SELECT
        COUNT_BIG(*) 
    FROM dbo.Test AS T 
        TABLESAMPLE (0 PERCENT) 
        REPEATABLE (' +
        (
            SELECT TOP (1)
                CONVERT(nvarchar(11), 0x7FFFFFFF - P.[partition_id] & 0xFFFFFFFF)
            FROM sys.partitions AS P
            WHERE
                P.[object_id] = OBJECT_ID(N'dbo.Test', N'U')
                AND P.index_id = 1
        ) + ')
        WITH (INDEX(0));';

PRINT @SQL;
--EXECUTE (@SQL);

Это выберет каждую строку на каждой странице, что бы ни TABLESAMPLEговорилось в предложении (даже ноль процентов).

Пол Уайт говорит, что GoFundMonica
источник
11

Это отличный вопрос! Я начну с того, что знаю наверняка, а затем перейду к предположениям. Много подробностей об этом в моем блоге здесь .

Выборочные обновления статистики используют TABLESAMPLEза кулисами. Это довольно легко найти документацию об этом в Интернете. Тем не менее, я считаю, что не очень хорошо известно, что строки, возвращаемые функцией TABLESAMPLEчастично, зависят hobt_idот объекта. Когда вы отбрасываете и воссоздаете объект, вы получаете новый, hobt_idпоэтому строки, возвращаемые случайной выборкой, различаются.

Если вы удалите и повторно вставите данные, они hobt_idостанутся прежними. Пока данные располагаются одинаково на диске (проверка порядка размещения возвращает одинаковые результаты в том же порядке), выборочные данные не должны изменяться.

Вы также можете изменить количество выбранных строк, перестроив кластеризованный индекс в таблице. Например:

UPDATE STATISTICS dbo.Test IX_Test_TextValue;

DBCC SHOW_STATISTICS('dbo.Test', IX_Test_TextValue) WITH STAT_HEADER; -- 273862 rows

ALTER INDEX PK_Test on Test REBUILD;

UPDATE STATISTICS dbo.Test IX_Test_TextValue;

DBCC SHOW_STATISTICS('dbo.Test', IX_Test_TextValue) WITH STAT_HEADER; -- 273320 rows

Что касается того, почему это происходит, я считаю, что это связано с тем, что SQL Server сканирует кластеризованный индекс вместо некластеризованного индекса при сборе выборочной статистики по индексу. Я также думаю, что есть скрытое (для тех из нас, кто отслеживает скрытые запросы на обновление статистики) значение для REPEATABLEиспользования с TABLESAMPLE. Я не доказал ничего из этого, но он объясняет, почему ваша гистограмма и выбранные строки изменяются с перестройкой кластерного индекса.

Джо Оббиш
источник
3

Я забыл, как работает TABLESAMPLE с точки зрения назначения случайной вероятности для каждой страницы. - Мартин Смит

Я видел это в Inside Microsoft SQL Server 2008: T-SQL Querying от Ицик Бен-Гана, и я не могу добавить его в качестве комментария, поэтому я публикую его здесь, я думаю, что это интересно и другим:

введите описание изображения здесь

Смотрите также Выборка с использованием TABLESAMPLE от Roji. П. Томас.

sepupic
источник