背景
使用以下形式的语句收集统计对象的数据:
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)
您可以使用扩展事件或事件探查器(SP:StmtCompleted
)收集此语句。
统计信息生成查询通常访问基表(而不是非聚集索引),以避免自然发生在非聚集索引页上的值的聚集。
采样的行数取决于为采样选择的整页数。该表的每个页面都被选中或未被选中。所选页面上的所有行都将有助于统计。
随机数
SQL Server使用随机数生成器来确定页面是否合格。在这种情况下使用的生成器是Lehmer随机数生成器,其参数值如下所示:
X 下一个 = X 种子 * 7 5 mod(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
子句中指定的值
- 对于sampled
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]
包含分区ID的低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 Lehmer随机数生成器的一个结果是,不应使用种子值零和int.max,因为这将导致算法生成零序列(选择每页)。
该代码检测到零,并在这种情况下使用系统“时钟”中的值作为种子。如果种子int.max它不这样做(0x7FFFFFFF
= 2 31 - 1)
我们可以设计这种情况,因为初始种子是作为分区ID的低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
子句说什么,都将选择每一页上的每一行(甚至为零)。