如何在统计中决定直方图步骤的数量

Pரத*_*ீப் 12 sql-server statistics

SQL Server 统计中的直方图步数是如何决定的?

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


演示

模式定义

CREATE TABLE histogram_step
  (
     id   INT IDENTITY(1, 1),
     name VARCHAR(50),
     CONSTRAINT pk_histogram_step PRIMARY KEY (id)
  )
Run Code Online (Sandbox Code Playgroud)

在我的表中插入 100 条记录

INSERT INTO histogram_step
            (name)
SELECT TOP 100 name
FROM   sys.syscolumns
Run Code Online (Sandbox Code Playgroud)

更新和检查统计信息

UPDATE STATISTICS histogram_step WITH fullscan

DBCC show_statistics('histogram_step', pk_histogram_step)
Run Code Online (Sandbox Code Playgroud)

直方图步骤:

+--------------+------------+---------+---------------------+----------------+
| 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 |
+--------------+------------+---------+---------------------+----------------+
Run Code Online (Sandbox Code Playgroud)

正如我们所看到的,直方图中有 53 个步骤。

再次插入几千条记录

INSERT INTO histogram_step
            (name)
SELECT TOP 10000 b.name
FROM   sys.syscolumns a
       CROSS JOIN sys.syscolumns b
Run Code Online (Sandbox Code Playgroud)

更新和检查统计信息

UPDATE STATISTICS histogram_step WITH fullscan

DBCC show_statistics('histogram_step', pk_histogram_step)
Run Code Online (Sandbox Code Playgroud)

现在直方图步骤减少到 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 |
+--------------+------------+---------+---------------------+----------------+
Run Code Online (Sandbox Code Playgroud)

再次插入几千条记录

INSERT INTO histogram_step
            (name)
SELECT TOP 100000 b.name
FROM   sys.syscolumns a
       CROSS JOIN sys.syscolumns b
Run Code Online (Sandbox Code Playgroud)

更新和检查统计信息

UPDATE STATISTICS histogram_step WITH fullscan

DBCC show_statistics('histogram_step', pk_histogram_step) 
Run Code Online (Sandbox Code Playgroud)

现在直方图步骤减少到 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 |
+--------------+------------+---------+---------------------+----------------+
Run Code Online (Sandbox Code Playgroud)

有人能告诉我这些步骤是如何决定的吗?

Joe*_*ish 15

我将把这篇文章限制在讨论单列统计信息,因为它已经很长了,而且您对 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);
Run Code Online (Sandbox Code Playgroud)

SQL Server 根据表的大小选择自动样本大小(我认为它是表中的行数和页数)。如果表格太大,则自动样本大小会低于 100%。这是我对 100 万行的同一张表的结果:

-- 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);
Run Code Online (Sandbox Code Playgroud)

TABLESAMPLE记录,但 StatMan 和 step_direction 未记录。这里 SQL Server 从表中抽取大约 66.6% 的数据来创建直方图。这意味着在更新FULLSCAN相同数据的统计数据(没有)时,您可以获得不同数量的直方图步骤。我从未在实践中观察到这一点,但我不明白为什么这是不可能的。

让我们对简单数据进行一些测试,看看统计数据是如何随时间变化的。下面是我编写的一些测试代码,用于将连续整数插入表中,在每次插入后收集统计信息,并将有关统计信息的信息保存到结果表中。让我们从一次插入 1 行开始,最多 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;
Run Code Online (Sandbox Code Playgroud)

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

第一张图

这是 10k 行的直方图:

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
Run Code Online (Sandbox Code Playgroud)

直方图只有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
Run Code Online (Sandbox Code Playgroud)

这有点令人失望。如果我们手动构建直方图,我们将为每个缺失值添加一个步骤。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
Run Code Online (Sandbox Code Playgroud)

如果我测试 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
Run Code Online (Sandbox Code Playgroud)

最终直方图:

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
Run Code Online (Sandbox Code Playgroud)

让我们用连续整数进一步测试,但表中有更多行。请注意,对于太小的表,手动指定样本大小将不起作用,因此我将在每次插入中添加 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);
Run Code Online (Sandbox Code Playgroud)

在这里,我插入了 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
Run Code Online (Sandbox Code Playgroud)

请注意,当在表中找到 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
Run Code Online (Sandbox Code Playgroud)

与上次测试没有实际区别。

现在让我们尝试插入字符,但在表中放置不同数量的每个字符。例如,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
Run Code Online (Sandbox Code Playgroud)

和以前一样,我仍然没有得到 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))
Run Code Online (Sandbox Code Playgroud)

这是表中行与直方图步骤的关系图: 图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
Run Code Online (Sandbox Code Playgroud)

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

我们可以从这一切中总结出什么?我无法证明这些,但这些观察似乎是正确的:

  • SQL Server 使用通用算法来创建直方图。对于某些数据分布,可以手动创建更完整的数据表示。
  • 如果表中有 NULL 数据并且 stats 查询找到它,那么 NULL 数据总是有自己的直方图步骤。
  • 在表中找到的最小值有自己的直方图步长RANGE_ROWS= 0。
  • 在表中找到的最大值将是表中的最后RANGE_HI_KEY一个。
  • 随着 SQL Server 对更多数据进行采样,它可能需要结合现有步骤为它找到的新数据腾出空间。如果您查看足够多的直方图,您可能会看到DISTINCT_RANGE_ROWS或 的常见值重复RANGE_ROWS。例如,255个显示开一堆倍RANGE_ROWSDISTINCT_RANGE_ROWS用于这里的最终测试的情况。
  • 对于简单的数据分布,您可能会看到 SQL Server 将顺序数据组合成一个不会导致信息丢失的直方图步骤。但是,当向数据添加间隙时,直方图可能不会按照您希望的方式进行调整。

这一切什么时候有问题?当由于直方图无法以查询优化器做出正确决策的方式表示数据分布而导致查询执行不佳时,这是一个问题。我认为有一种倾向认为,有更多的直方图步骤总是更好,并且当 SQL Server 生成数百万行或更多行的直方图但不使用 200 或 201 个直方图步骤时会感到震惊。然而,即使直方图有 200 或 201 步,我也看到了很多统计问题。我们无法控制 SQL Server 为统计对象生成多少直方图步骤,因此我不会担心。但是,当您遇到由统计问题导致的查询性能不佳时,您可以采取一些步骤。我将给出一个非常简短的概述。

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

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

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

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

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

在这里可以运行更多测试,因此我鼓励您进行试验。我在 SQL Server 2014 express 上做了这个测试,所以真的没有什么能阻止你。

  • 请参阅 https://www.google.com/patents/US6714938 (4认同)