样本大小的异常行为可用于统计信息更新


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*279319240 = 1144*279 + 64324198=1162*279314154=1126所以方差的采样页数。
马丁·史密斯

@MartinSmithJust的一个文件- 279数字是有趣的,我就喜欢了解所涉及的图案
马修McGiffen

Answers:


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)

您可以使用扩展事件或事件探查器(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子句说什么,都将选择每一页上的每一行(甚至为零)。


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。我还没有证明这一点,但是它解释了为什么直方图和采样的行会随着聚簇索引的重建而变化。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.