SQL Serverに200 GBのデータウェアハウスがあります。
一部のクエリの実行時間が非常に遅くなっています。たとえば、inner join
を使用した単純なdelete
クエリの12時間。
実行計画でいくつかの調査を行った後、WITH FULLSCAN
オプションを使用して、クエリに関連する2つのテーブルの統計を更新しました。
クエリは1秒未満で実行されるようになったため、統計は最新ではなかったようです。
データベースでauto update statistics
を無効にし、データウェアハウスの読み込み後に手動でUPDATE STATISTICS
を実行することを検討しています。データウェアハウスは、ソースから増分的にロードされますERPシステムは毎日、夜間です。
データウェアハウジングシナリオでのauto update statistics
は実際には役に立たないと想定しても正しいですか?代わりに、データのロード後に統計を手動で更新する方が理にかなっていますか?
統計の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);
_
これで、統計用地で何が起こったかを確認できます。
_select *
from sys.stats
where object_id = OBJECT_ID('dbo.test_table')
_
ただし、これが意味のある統計オブジェクトであるかどうかを確認するには、次のことを行う必要があります。
_dbcc show_statistics('dbo.test_table',cix_test_table)
_
したがって、この統計は更新されていません。これは、統計情報が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
_
Auto_update統計を盲目的に無効にする代わりに、データセットのスキューを調べます。データに大きなスキューがある場合は、フィルタリングされた統計の作成を検討し、、次にを管理するかどうかを決定する必要があります統計の更新は手動で行うのが正しい方法です。
スキューを分析するには、調べたい特定のstat/indexの組み合わせでDBCC SHOW_STATISTICS(<stat_object>, <index_name>);
(上記のスクリプトでは_WITH STAT_HEADER
_なし)を実行する必要があります。スキューを目撃する簡単な方法は、ヒストグラム(3番目の結果セット)を見て、_EQ_ROWS
_の分散を確認することです。それがかなり一貫しているなら、あなたのスキューは最小限です。これをステップアップするには、_RANGE_ROWS
_列を見て分散を調べます。これにより、各ステップ間に存在する行数が測定されるためです。最後に、_[All density]
_(2番目の結果セット)から_DENSITY_VECTOR
_の結果を取得し、その結果に_[Rows Sampled]
_(最初の結果セット)の_STAT_HEADER
_値を乗算して、その列のクエリに対する平均期待値は、次のようになります。その平均を_EQ_ROWS
_と比較し、大幅に変動する場所が多数ある場合は、スキューがあります。
スキューがあることがわかった場合は、_RANGE_ROWS
_が非常に高い範囲でフィルター処理された統計を作成することを検討する必要があります。そうすることで、これらの値をより適切に推定するための追加の手順を実行できます。
これらのフィルタリングされた統計を配置したら、手動で統計を更新する可能性を確認できます。