Дивна поведінка з розмірами вибірки для оновлення статистики


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.
Мартін Сміт

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)

Ви можете зібрати цю заяву за допомогою Extended Events або Profiler ( SP:StmtCompleted).

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

Кількість рядків, відібраних у вибірку, залежить від кількості цілих сторінок, вибраних для вибірки. Кожна сторінка таблиці або вибрана, або її немає. Усі рядки на вибраних сторінках вносять свій внесок у статистику.

Випадкові числа

SQL Server використовує генератор випадкових чисел, щоб вирішити, чи відповідає сторінка чи ні. Генератором, що використовується в цьому випадку, є генератор випадкових чисел Лемера зі значеннями параметрів, як показано нижче:

X наступний = X насіння * 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]

де пам'ять at [rcx+30h]містить низькі 32 біта ідентифікатора розділу, а пам'ять 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 Лемера є те, що значення насіння нуля та int.max не повинні використовуватися, оскільки це призведе до того, що алгоритм створить послідовність нулів (вибір кожної сторінки).

Код виявляє нуль і в цьому випадку використовує значення системи "clock" як насіння. Це не робиться так, якщо насіння є 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пункту (навіть нульовий відсоток).


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 by Itzik Ben-Gan, і я не можу додати це як коментар, тому я публікую його тут, я думаю, що це цікаво і іншим:

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

Дивіться також Вибірка з використанням ТАБЛЕСАМПЛО Roji . П. Томас.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.