Фон
Дані для об'єкта статистики збираються за допомогою форми заяви:
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
пункту (навіть нульовий відсоток).