如何在统计中确定直方图的步数


11

如何在SQL Server的“统计信息”中确定直方图步数?

为什么即使我的键列有200多个不同的值,它也限制为200步?有决定因素吗?


演示版

模式定义

CREATE TABLE histogram_step
  (
     id   INT IDENTITY(1, 1),
     name VARCHAR(50),
     CONSTRAINT pk_histogram_step PRIMARY KEY (id)
  )

在表中插入100条记录

INSERT INTO histogram_step
            (name)
SELECT TOP 100 name
FROM   sys.syscolumns

更新和检查统计信息

UPDATE STATISTICS histogram_step WITH fullscan

DBCC show_statistics('histogram_step', pk_histogram_step)

直方图步骤:

+--------------+------------+---------+---------------------+----------------+
| RANGE_HI_KEY | RANGE_ROWS | EQ_ROWS | DISTINCT_RANGE_ROWS | AVG_RANGE_ROWS |
+--------------+------------+---------+---------------------+----------------+
|            1 |          0 |       1 |                   0 |              1 |
|            3 |          1 |       1 |                   1 |              1 |
|            5 |          1 |       1 |                   1 |              1 |
|            7 |          1 |       1 |                   1 |              1 |
|            9 |          1 |       1 |                   1 |              1 |
|           11 |          1 |       1 |                   1 |              1 |
|           13 |          1 |       1 |                   1 |              1 |
|           15 |          1 |       1 |                   1 |              1 |
|           17 |          1 |       1 |                   1 |              1 |
|           19 |          1 |       1 |                   1 |              1 |
|           21 |          1 |       1 |                   1 |              1 |
|           23 |          1 |       1 |                   1 |              1 |
|           25 |          1 |       1 |                   1 |              1 |
|           27 |          1 |       1 |                   1 |              1 |
|           29 |          1 |       1 |                   1 |              1 |
|           31 |          1 |       1 |                   1 |              1 |
|           33 |          1 |       1 |                   1 |              1 |
|           35 |          1 |       1 |                   1 |              1 |
|           37 |          1 |       1 |                   1 |              1 |
|           39 |          1 |       1 |                   1 |              1 |
|           41 |          1 |       1 |                   1 |              1 |
|           43 |          1 |       1 |                   1 |              1 |
|           45 |          1 |       1 |                   1 |              1 |
|           47 |          1 |       1 |                   1 |              1 |
|           49 |          1 |       1 |                   1 |              1 |
|           51 |          1 |       1 |                   1 |              1 |
|           53 |          1 |       1 |                   1 |              1 |
|           55 |          1 |       1 |                   1 |              1 |
|           57 |          1 |       1 |                   1 |              1 |
|           59 |          1 |       1 |                   1 |              1 |
|           61 |          1 |       1 |                   1 |              1 |
|           63 |          1 |       1 |                   1 |              1 |
|           65 |          1 |       1 |                   1 |              1 |
|           67 |          1 |       1 |                   1 |              1 |
|           69 |          1 |       1 |                   1 |              1 |
|           71 |          1 |       1 |                   1 |              1 |
|           73 |          1 |       1 |                   1 |              1 |
|           75 |          1 |       1 |                   1 |              1 |
|           77 |          1 |       1 |                   1 |              1 |
|           79 |          1 |       1 |                   1 |              1 |
|           81 |          1 |       1 |                   1 |              1 |
|           83 |          1 |       1 |                   1 |              1 |
|           85 |          1 |       1 |                   1 |              1 |
|           87 |          1 |       1 |                   1 |              1 |
|           89 |          1 |       1 |                   1 |              1 |
|           91 |          1 |       1 |                   1 |              1 |
|           93 |          1 |       1 |                   1 |              1 |
|           95 |          1 |       1 |                   1 |              1 |
|           97 |          1 |       1 |                   1 |              1 |
|           99 |          1 |       1 |                   1 |              1 |
|          100 |          0 |       1 |                   0 |              1 |
+--------------+------------+---------+---------------------+----------------+

如我们所见,直方图中有53个步骤。

再次插入几千条记录

INSERT INTO histogram_step
            (name)
SELECT TOP 10000 b.name
FROM   sys.syscolumns a
       CROSS JOIN sys.syscolumns b

更新和检查统计信息

UPDATE STATISTICS histogram_step WITH fullscan

DBCC show_statistics('histogram_step', pk_histogram_step)

现在直方图的步骤减少到4个步骤

+--------------+------------+---------+---------------------+----------------+
| RANGE_HI_KEY | RANGE_ROWS | EQ_ROWS | DISTINCT_RANGE_ROWS | AVG_RANGE_ROWS |
+--------------+------------+---------+---------------------+----------------+
|            1 |          0 |       1 |                   0 |              1 |
|        10088 |      10086 |       1 |               10086 |              1 |
|        10099 |         10 |       1 |                  10 |              1 |
|        10100 |          0 |       1 |                   0 |              1 |
+--------------+------------+---------+---------------------+----------------+

再次插入几千条记录

INSERT INTO histogram_step
            (name)
SELECT TOP 100000 b.name
FROM   sys.syscolumns a
       CROSS JOIN sys.syscolumns b

更新和检查统计信息

UPDATE STATISTICS histogram_step WITH fullscan

DBCC show_statistics('histogram_step', pk_histogram_step) 

现在直方图的步骤减少到3个步骤

+--------------+------------+---------+---------------------+----------------+
| RANGE_HI_KEY | RANGE_ROWS | EQ_ROWS | DISTINCT_RANGE_ROWS | AVG_RANGE_ROWS |
+--------------+------------+---------+---------------------+----------------+
|            1 |          0 |       1 |                   0 |              1 |
|       110099 |     110097 |       1 |              110097 |              1 |
|       110100 |          0 |       1 |                   0 |              1 |
+--------------+------------+---------+---------------------+----------------+

有人可以告诉我如何确定这些步骤吗?


3
200是一个任意选择。它与您在特定表中有多少个不同的值无关。如果您想知道为什么选择200个,则必须询问1990年代SQL Server团队的工程师,而不是您的同行
Aaron Bertrand

1
@AaronBertrand-谢谢..那么如何确定这些步骤的数量
Pரதீப்17年

1
没有决定。上限为200。好吧,从技术上讲,它是201,但这是另一回事了。
亚伦·贝特朗

1
我曾问intrastep估计类似的问题,可能会有所帮助dba.stackexchange.com/questions/148523/...
jesijesi

Answers:


14

我将把这篇文章限制为讨论单列统计信息,因为它已经很长了,并且您对SQL Server如何将数据存储到直方图步骤中感兴趣。对于多列统计信息,直方图仅在前导列上创建。

当SQL Server确定需要更新统计信息时,它将启动一个隐藏查询,该查询读取表的所有数据或表的数据样本。您可以查看带有扩展事件的这些查询。StatMan在SQL Server中有一个与创建直方图有关的函数。对于简单的统计对象,至少有两种不同类型的StatMan查询(对于快速统计更新有不同的查询,我怀疑分区表上的增量统计功能也使用了不同的查询)。

第一个只是从表中获取所有数据,而没有任何过滤。您可以在表格很小或使用以下FULLSCAN选项收集统计信息时看到此信息:

CREATE TABLE X_SHOW_ME_STATMAN (N INT);
CREATE STATISTICS X_STAT_X_SHOW_ME_STATMAN ON X_SHOW_ME_STATMAN (N);

-- after gathering stats with 1 row in table
SELECT StatMan([SC0]) FROM
(
    SELECT TOP 100 PERCENT [N] AS [SC0] 
    FROM [dbo].[X_SHOW_ME_STATMAN] WITH (READUNCOMMITTED)
    ORDER BY [SC0] 
) AS _MS_UPDSTATS_TBL 
OPTION (MAXDOP 16);

SQL Server根据表的大小选择自动样本大小(我认为这是表中的行数和页数)。如果表格太大,则自动样本大小将降至100%以下。这是我对具有1M行的同一个表所得到的:

-- after gathering stats with 1 M rows in table
SELECT StatMan([SC0], [SB0000]) FROM 
(
    SELECT TOP 100 PERCENT [SC0], step_direction([SC0]) over (order by NULL) AS [SB0000] 
    FROM 
    (
        SELECT [N] AS [SC0] 
        FROM [dbo].[X_SHOW_ME_STATMAN] TABLESAMPLE SYSTEM (6.666667e+001 PERCENT) WITH (READUNCOMMITTED) 
    ) AS _MS_UPDSTATS_TBL_HELPER 
    ORDER BY [SC0], [SB0000] 
) AS _MS_UPDSTATS_TBL
OPTION (MAXDOP 1);

TABLESAMPLE记录,但没有StatMan和step_direction。在此,SQL Server从表中采样了大约66.6%的数据以创建直方图。这意味着更新FULLSCAN同一数据上的统计信息时(没有),您可以获得不同数量的直方图步数。我从没有在实践中观察到这种情况,但是我不明白为什么它不可能。

让我们对简单数据进行一些测试,以查看统计信息随着时间的变化。以下是我编写的一些测试代码,用于将顺序整数插入表中,在每次插入后收集统计信息,并将有关统计信息保存到结果表中。让我们从一次最多插入10000行开始。测试床:

DECLARE
@stats_id INT,
@table_object_id INT,
@rows_per_loop INT = 1,
@num_of_loops INT = 10000,
@loop_num INT;

BEGIN
    SET NOCOUNT ON;

    TRUNCATE TABLE X_STATS_RESULTS;

    SET @table_object_id = OBJECT_ID ('X_SEQ_NUM');
    SELECT @stats_id = stats_id FROM sys.stats
    WHERE OBJECT_ID = @table_object_id
    AND name = 'X_STATS_SEQ_INT_FULL';

    SET @loop_num = 0;
    WHILE @loop_num < @num_of_loops
    BEGIN
        SET @loop_num = @loop_num + 1;

        INSERT INTO X_SEQ_NUM WITH (TABLOCK)
        SELECT @rows_per_loop * (@loop_num - 1) + N FROM dbo.GetNums(@rows_per_loop);

        UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN; -- can comment out FULLSCAN as needed

        INSERT INTO X_STATS_RESULTS WITH (TABLOCK)
        SELECT 'X_STATS_SEQ_INT_FULL', @rows_per_loop * @loop_num, rows_sampled, steps 
        FROM sys.dm_db_stats_properties(@table_object_id, @stats_id);
        END;
END;

对于此数据,直方图的步数迅速增加到200(它首先达到397行的最大步数),一直保持199或200,直到表中有1485行,然后缓慢减少直到直方图只有3或4脚步。这是所有数据的图表:

第一张图

这是1万行的直方图:

RANGE_HI_KEY    RANGE_ROWS  EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
1               0           1       0                   1
9999            9997        1       9997                1
10000           0           1       0                   1

直方图只有3个步骤是否有问题?从我们的角度来看,信息似乎已被保留。请注意,由于数据类型为INTEGER,我们可以找出表中每个整数(从1到10000)中有多少行。通常,SQL Server也可以弄清楚这一点,尽管在某些情况下,这种方法不太可行。有关示例,请参见此SE帖子

如果我们从表格中删除一行并更新统计信息,您会怎么办?理想情况下,我们将获得另一个直方图步骤,以显示表中不再存在丢失的整数。

DELETE FROM X_SEQ_NUM
WHERE X_NUM  = 1000;

UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;

DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- still 3 steps

DELETE FROM X_SEQ_NUM
WHERE X_NUM  IN (2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000);

UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;

DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- still 3 steps

有点令人失望。如果我们要手工构建直方图,我们将为每个缺失值添加一个步骤。SQL Server使用的是通用算法,因此对于某些数据集,我们可能能够提供比其使用的代码更合适的直方图。当然,从表中获取0行或1行之间的实际差别很小。使用20000行进行测试时,我得到相同的结果,其中每个整数在表中具有2行。当我删除数据时,直方图不会增加步数。

RANGE_HI_KEY    RANGE_ROWS  EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
1               0           2       0                   1
9999            19994       2       9997                2
10000           0           2       0                   1

如果我测试100万行,并且每个整数在表中具有100行,则结果会稍好一些,但我仍然可以手动构建更好的直方图。

truncate table X_SEQ_NUM;

BEGIN TRANSACTION;
INSERT INTO X_SEQ_NUM WITH (TABLOCK)
SELECT N FROM dbo.GetNums(10000);
GO 100
COMMIT TRANSACTION;

UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;

DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- 4 steps

DELETE FROM X_SEQ_NUM
WHERE X_NUM  = 1000;

UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;

DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- now 5 steps with a RANGE_HI_KEY of 998 (?)

DELETE FROM X_SEQ_NUM
WHERE X_NUM  IN (2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000);

UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;

DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- still 5 steps

最终直方图:

RANGE_HI_KEY    RANGE_ROWS  EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
1               0           100     0                   1
998             99600       100     996                 100
3983            298100      100     2981                100
9999            600900      100     6009                100
10000           0           100     0                   1

让我们用顺序整数进一步测试,但表中有更多行。请注意,对于太小的表,手动指定样本大小将无效,因此我将在每个插入中添加100行,并且每次最多收集100万行的统计信息。我看到了与以前类似的模式,除了一旦我获得了表中的637300行,就不再使用默认采样率对表中的行进行100%的采样。随着行数的增加,直方图步数增加。也许这是因为随着表中未采样的行数增加,SQL Server最终会在数据中留下更多的空白。即使在100万行时,我也不会达到200步,但是如果我不断添加行,我希望我能到达那里并最终开始下降。

图2

X轴是表格中的行数。随着行数的增加,采样的行会有所不同,并且不会超过650k。

现在让我们对VARCHAR数据进行一些简单的测试。

CREATE TABLE X_SEQ_STR (X_STR VARCHAR(5));
CREATE STATISTICS X_SEQ_STR ON X_SEQ_STR(X_STR);

在这里,我要插入200个数字(作为字符串)以及NULL。

INSERT INTO X_SEQ_STR
SELECT N FROM dbo.GetNums(200)
UNION ALL
SELECT NULL;

UPDATE STATISTICS X_SEQ_STR X_SEQ_STR ;

DBCC SHOW_STATISTICS ('X_SEQ_STR', 'X_SEQ_STR'); -- 111 steps, RANGE_ROWS is 0 or 1 for all steps

请注意,在表中找到NULL时,总是得到自己的直方图步骤。SQL Server可以给我确切的201个步骤来保留所有信息,但是它没有这样做。从技术上讲,信息丢失是因为“ 1111”在“ 1”和“ 2”之间排序。

现在让我们尝试插入不同的字符,而不只是整数:

truncate table X_SEQ_STR;

INSERT INTO X_SEQ_STR
SELECT CHAR(10 + N) FROM dbo.GetNums(200)
UNION ALL
SELECT NULL;

UPDATE STATISTICS X_SEQ_STR X_SEQ_STR ;

DBCC SHOW_STATISTICS ('X_SEQ_STR', 'X_SEQ_STR'); -- 95 steps, RANGE_ROWS is 0 or 1 or 2

与上次测试没有实际差异。

现在让我们尝试插入字符,但是在表中放置每个字符不同的数字。例如,CHAR(11)有1行,CHAR(12)有2行,依此类推。

truncate table X_SEQ_STR;

DECLARE
@loop_num INT;

BEGIN
    SET NOCOUNT ON;

    SET @loop_num = 0;
    WHILE @loop_num < 200
    BEGIN
        SET @loop_num = @loop_num + 1;

        INSERT INTO X_SEQ_STR WITH (TABLOCK)
        SELECT CHAR(10 + @loop_num) FROM dbo.GetNums(@loop_num);
    END;
END;

UPDATE STATISTICS X_SEQ_STR X_SEQ_STR ;

DBCC SHOW_STATISTICS ('X_SEQ_STR', 'X_SEQ_STR'); -- 148 steps, most with RANGE_ROWS of 0

和以前一样,我仍然无法获得200个直方图步长。但是,许多步骤RANGE_ROWS的值为0。

对于最终测试,我将在每个循环中插入5个字符的随机字符串,并每次收集统计信息。这是随机字符串的代码:

char((rand()*25 + 65))+char((rand()*25 + 65))+char((rand()*25 + 65))+char((rand()*25 + 65))+char((rand()*25 + 65))

这是表格与直方图步骤中的行图: 图3

请注意,步数一旦开始向上和向下,就不会降至100以下。我从某个地方听说过(但现在无法获取它),SQL Server直方图构建算法将直方图步骤组合在一起,因为它们的空间不足。因此,仅需添加少量数据,您就可以在步骤数上发生巨大的变化。这是我发现有趣的数据示例:

ROWS_IN_TABLE   ROWS_SAMPLED    STEPS
36661           36661           133
36662           36662           143
36663           36663           143
36664           36664           141
36665           36665           138

即使使用进行采样FULLSCAN,添加一行也可以使步数增加10,保持不变,然后减少2,然后减少3。

我们可以从所有这些总结什么?我无法证明其中的任何一个,但是这些观察似乎成立:

  • SQL Server使用通用算法创建直方图。对于某些数据分发,可以手动创建更完整的数据表示形式。
  • 如果表中有NULL数据,并且stats查询找到它,则该NULL数据将始终获得其自己的直方图步骤。
  • 在表中找到的最小值具有RANGE_ROWS= 0的自己的直方图步长。
  • 在表中找到的最大值将是表中的最终值RANGE_HI_KEY
  • 当SQL Server采样更多数据时,可能需要合并现有步骤,以便为其找到的新数据腾出空间。如果查看足够的直方图,您可能会看到重复通用值DISTINCT_RANGE_ROWSRANGE_ROWS。例如,这里的255 RANGE_ROWSDISTINCT_RANGE_ROWS最终测试用例的显示次数都是很多的。
  • 对于简单的数据分发,您可能会看到SQL Server将顺序数据组合到一个直方图步骤中,这不会导致信息丢失。但是,在向数据添加间隙时,直方图可能无法按照您希望的方式进行调整。

什么时候所有这些问题?当查询由于直方图而不能以一种表示查询优化器做出好的决策的方式来表示数据分布的方式而导致性能下降时,就会出现问题。我认为有一种趋势认为,拥有更多的直方图步长总是更好,并且当SQL Server在数百万行或更多的行上生成直方图但不完全使用200或201直方图步长时会感到con异。但是,即使直方图有200步或201步,我也看到了很多统计问题。我们无法控制SQL Server为一个统计对象生成多少个直方图步骤,因此我不必担心。但是,当您遇到由于统计问题而导致查询效果不佳时,可以采取一些措施。我将给出一个非常简短的概述。

在某些情况下,完全收集统计信息可能会有所帮助。对于非常大的表,自动样本大小可能小于表中行的1%。有时,这可能会导致错误的计划,具体取决于列中的数据中断。Microsoft的CREATE STATISTICSUPDATE STATISTICS的文档说得最多:

SAMPLE对于基于默认采样的查询计划不是最佳的特殊情况很有用。在大多数情况下,无需指定SAMPLE,因为查询优化器已经使用了采样并默认情况下确定了具有统计意义的样本大小,这是创建高质量查询计划所必需的。

对于大多数工作负载,不需要全面扫描,并且默认采样就足够了。但是,某些对广泛变化的数据分布敏感的工作负载可能需要增加样本量,甚至进行完整扫描。

在某些情况下,创建过滤的统计信息会有所帮助。您可能有一列包含倾斜的数据和许多不同的不同值。如果数据中经常过滤某些值,则可以仅针对那些常用值创建统计直方图。查询优化器可以使用在较小范围的数据上定义的统计信息,而不是在所有列值上定义的统计信息。您仍然不能保证在直方图中获得200步,但是如果仅在一个值上创建过滤的统计信息,则直方图将在该值上步。

使用分区视图是一种有效获取表200多个步骤的方法。假设您可以轻松地将一张大桌子每年拆分成一张桌子。您创建一个UNION ALL合并了所有年度表的视图。每个表都有自己的直方图。请注意,SQL Server 2014中引入的新增量统计信息仅使统计信息更新更加有效。查询优化器将不使用每个分区创建的统计信息。

还有更多测试可以在这里进行,因此我鼓励您尝试一下。我在SQL Server 2014 Express上进行了此测试,因此确实没有什么可以阻止您。


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.