Задний план
Данные для объекта статистики собираются с помощью оператора в форме:
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
говорилось в предложении (даже ноль процентов).