我应该在数据仓库场景中禁用“自动更新统计”吗?

sas*_*aso 12 data-warehouse sql-server statistics

我在 SQL Server 中有 200 GB 的数据仓库。

对于某些查询,我遇到了非常缓慢的执行时间;例如delete,使用inner join.

在对执行计划进行了一些研究之后,我使用该WITH FULLSCAN选项更新了查询中涉及的 2 个表的统计信息。

查询现在在不到一秒的时间内执行,因此统计信息似乎不是最新的。

我正在考虑禁用auto update statistics数据库并UPDATE STATISTICS在加载数据仓库后手动运行。每天晚上从源 ERP 系统增量加载数据仓库。

我假设auto update statistics在数据仓库场景中不是真的有用吗?相反,在数据加载后手动更新统计数据是否更有意义?

swa*_*eck 11

这是有关何时发生统计数据的 auto_update 的白皮书。以下是与统计数据的自动更新相比的要点:

  • 表大小已从 0 行变为 >0 行(测试 1)。
  • 收集统计信息时表中的行数为 500 或更少,并且统计对象的前导列的 colmodctr 自此更改了 500 多(测试 2)。
  • 统计时表有500多行,统计对象前列的colmodctr变化了500+表行数的20%以上(测试3) .

所以@JNK 在评论中指出,如果表中有 10 亿行,则需要对统计信息的第一列进行 20,000,5000 次写入才能触发更新。

让我们采用以下结构:

CREATE TABLE dbo.test_table (
    test_table_id INTEGER IDENTITY(1,1) NOT NULL, 
    test_table_value VARCHAR(50), 
    test_table_value2 BIGINT, 
    test_table_value3 NUMERIC(10,2)
);

CREATE CLUSTERED INDEX cix_test_table ON dbo.test_table (test_table_id, test_table_value);
Run Code Online (Sandbox Code Playgroud)

现在我们可以检查一下统计领域发生了什么。

select * 
    from sys.stats
        where object_id = OBJECT_ID('dbo.test_table')
Run Code Online (Sandbox Code Playgroud)

stat_container

但是,要查看这是否是一个有意义的统计对象,我们需要:

dbcc show_statistics('dbo.test_table',cix_test_table)
Run Code Online (Sandbox Code Playgroud)

直方图

所以这个统计数据还没有更新。那是因为看起来统计数据在SELECT发生之前不会更新,即便如此,它SELECT也必须超出 SQL Server 在其直方图中的内容。这是我用来测试的测试脚本:

    CREATE TABLE test_table (
        test_table_id INTEGER IDENTITY(1,1) NOT NULL, 
        test_table_value VARCHAR(50), 
        test_table_value2 BIGINT, 
        test_table_value3 NUMERIC(10,2)
    );

    CREATE CLUSTERED INDEX cix_test_table ON test_table (test_table_id, test_table_value);

    ALTER TABLE test_table ADD CONSTRAINT pk_test_table PRIMARY KEY  (test_table_id)

    SELECT * 
        FROM sys.stats
            WHERE object_id = OBJECT_ID('dbo.test_table')

    --DBCC SHOW_STATISTICS('dbo.test_table',pk_test_table)
    DBCC SHOW_STATISTICS('dbo.test_table',cix_test_table) WITH STAT_HEADER;

declare @test int = 0

WHILE @test < 1
    BEGIN
        INSERT INTO test_table (test_table_value,test_table_value2,test_table_value3) VALUES
            ('stats test' + CAST(@test AS VARCHAR(10)),@test, @test)
        SET @test = @test + 1;
    END

SELECT 'one row|select < 1', * FROM test_table WHERE test_table_id < 1;
--DBCC SHOW_STATISTICS('dbo.test_table',pk_test_table);
DBCC SHOW_STATISTICS('dbo.test_table',cix_test_table) WITH STAT_HEADER;

SET @test = 1

WHILE @test < 500
    BEGIN
        INSERT INTO test_table (test_table_value,test_table_value2,test_table_value3) VALUES
            ('stats test' + CAST(@test AS VARCHAR(10)),@test, @test)
        SET @test = @test + 1;
    END

SELECT '100 rows(add 99)|select < 100',* FROM test_table WHERE test_table_id < 100;
--DBCC SHOW_STATISTICS('dbo.test_table',pk_test_table);
DBCC SHOW_STATISTICS('dbo.test_table',cix_test_table) WITH STAT_HEADER;
--get the table up to 500 rows/changes
WHILE @test < 500
    BEGIN
        INSERT INTO test_table (test_table_value,test_table_value2,test_table_value3) VALUES
            ('stats test' + CAST(@test AS VARCHAR(10)),@test, @test)
        SET @test = @test + 1;
    END
SELECT '500 rows(add 400)|select < 100',* FROM test_table WHERE test_table_id < 100;
DBCC SHOW_STATISTICS('dbo.test_table',cix_test_table) WITH STAT_HEADER;
SELECT '500 rows(add 400)|select < 500',* FROM test_table WHERE test_table_id < 500;
--DBCC SHOW_STATISTICS('dbo.test_table',pk_test_table);
DBCC SHOW_STATISTICS('dbo.test_table',cix_test_table) WITH STAT_HEADER;
--bump it to 501
SET @test = 500;
WHILE @test < 501
    BEGIN
        INSERT INTO test_table (test_table_value,test_table_value2,test_table_value3) VALUES
            ('stats test' + CAST(@test AS VARCHAR(10)),@test, @test)
        SET @test = @test + 1;
    END


SELECT '501 rows(add 1)|select < 501',* FROM test_table WHERE test_table_id < 501;
--DBCC SHOW_STATISTICS('dbo.test_table',pk_test_table);
DBCC SHOW_STATISTICS('dbo.test_table',cix_test_table) WITH STAT_HEADER;

--bump it to 600
SET @test = 501;
WHILE @test < 600
    BEGIN
        INSERT INTO test_table (test_table_value,test_table_value2,test_table_value3) VALUES
            ('stats test' + CAST(@test AS VARCHAR(10)),@test, @test)
        SET @test = @test + 1;
    END

SELECT '600 rows (add 100)|select < 600',* FROM test_table WHERE test_table_id < 600;
--DBCC SHOW_STATISTICS('dbo.test_table',pk_test_table);
DBCC SHOW_STATISTICS('dbo.test_table',cix_test_table) WITH STAT_HEADER;

--bump it to 700
SET @test = 600;
WHILE @test < 700
    BEGIN
        INSERT INTO test_table (test_table_value,test_table_value2,test_table_value3) VALUES
            ('stats test' + CAST(@test AS VARCHAR(10)),@test, @test)
        SET @test = @test + 1;
    END

SELECT '700 rows (add 100)|select < 700', * FROM test_table WHERE test_table_id < 700;
--DBCC SHOW_STATISTICS('dbo.test_table',pk_test_table);
DBCC SHOW_STATISTICS('dbo.test_table',cix_test_table) WITH STAT_HEADER;

--bump it to 1200
SET @test = 700;
WHILE @test < 1200
    BEGIN
        INSERT INTO test_table (test_table_value,test_table_value2,test_table_value3) VALUES
            ('stats test' + CAST(@test AS VARCHAR(10)),@test, @test)
        SET @test = @test + 1;
    END

SELECT '1200 rows (add 500)|select < 1200',* FROM test_table WHERE test_table_id < 1200;
--DBCC SHOW_STATISTICS('dbo.test_table',pk_test_table);
DBCC SHOW_STATISTICS('dbo.test_table',cix_test_table) WITH STAT_HEADER;
--DROP TABLE test_table
Run Code Online (Sandbox Code Playgroud)

我不会盲目地禁用 auto_update 统计信息,而是尝试检查您的数据集是否存在偏差。如果您的数据表现出明显的偏差,那么您需要考虑创建过滤的统计信息,然后决定手动管理统计信息更新是否是正确的做法。

要分析偏斜,您需要DBCC SHOW_STATISTICS(<stat_object>, <index_name>);WITH STAT_HEADER要检查的特定统计/索引组合上运行(在上述脚本中不带)。观察您的偏差的一种快速方法是查看直方图(第三个结果集)并检查EQ_ROWS. 如果它相当一致,那么你的偏斜是最小的。为了更进一步,您查看RANGE_ROWS列并查看那里的差异,因为这衡量了每个步骤之间存在的行数。最后,您可以[All density]DENSITY_VECTOR(第二个结果集)中获取结果并将其乘以(第一个结果集)中的[Rows Sampled]值,STAT_HEADER然后查看对该列查询的平均期望值。你将该平均值与你的EQ_ROWS 如果有很多地方变化很大,那么你就有了偏差。

如果您发现确实存在偏差,那么您需要考虑在具有 high 非常 high 的范围上创建一些过滤统计,RANGE_ROWS以便您可以提供额外的步骤来更好地估计这些值。

一旦您准备好这些过滤的统计信息,您就可以查看手动更新统计信息的可能性。