テーブルのCacheId列にカスタム統計が存在します。一晩の統計更新後:
Statistics for INDEX 'ST_TableName_CacheId'.
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Name Updated Rows Rows Sampled Steps Density Average Key Length String Index
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
ST_TableName_CacheId Apr 26 2014 2:04AM 121482 121482 6 0 4 NO 121482
All Density Average Length Columns
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
0.1666667 4 CacheId
Histogram Steps
RANGE_HI_KEY RANGE_ROWS EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
39968 0 20247 0 1
40058 0 20247 0 1
40062 0 20247 0 1
40066 0 20247 0 1
40069 0 20247 0 1
41033 0 20247 0 1
1)CacheId = 41033であるこのテーブルの既存のデータセットに対する結合のパフォーマンスは、良好な見積もり(23622対20247の実際)で良好に実行されます。
2)次に、20247行のCacheId = 41273で挿入が実行されます。
3)次に、この新しく挿入されたデータセットに対する結合は、1行の推定が不十分であることを示しており、その結果、計画が悪くなります。
4)統計を手動で更新すると(元はフルスキャンでした)、新しいヒストグラムが表示されます。
Statistics for INDEX 'ST_TableName_CacheId'.
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Name Updated Rows Rows Sampled Steps Density Average Key Length String Index
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
ST_TableName_CacheId Apr 28 2014 10:41AM 141729 141729 7 0 4 NO 141729
All Density Average Length Columns
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
0.1428571 4 CacheId
Histogram Steps
RANGE_HI_KEY RANGE_ROWS EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
39968 0 20247 0 1
40058 0 20247 0 1
40062 0 20247 0 1
40066 0 20247 0 1
40069 0 20247 0 1
41033 0 20247 0 1
41274 0 20247 0 1
5)CacheId = 41274に対して同じ結合クエリを再度実行すると、完全な推定(20247)と良好なパフォーマンスが示されます。
Q1)元の推定値が数学的にそれほど悪いのはなぜですか?つまり、CacheIdはスパースですが、20000:1の比率ではありません。
Q2) cacheIdの数が増えると、新しく挿入されたデータの見積もりが自然に改善されると思いますか?
Q3)新しいデータセットが挿入されるたびに統計情報を更新する必要なしに、推定を改善する(または1行の確実性を低くする)方法(gulp、trickなど)はありますか(たとえば、はるかに大きいCacheId = 999999で偽のデータセットを追加します)。
以下は、テーブル内のすべてのCacheIdの実際の行数です。
CacheId Rows
39968 20247
40058 20247
40062 20247
40066 20247
40069 20247
41033 20247
41274 20247
[この質問に回答するためにQPが必要だとは思わない。必要に応じて特定の質問に答えることができます! ]
Q1)元の見積もりが数学的にそれほど悪いのはなぜですか?つまり、CacheIdはスパースですが、20000:1の比率ではありません。
以下は、統計の自動更新をトリガーするルールです SQL Serverの統計メンテナンス機能(自動統計) :
上記のアルゴリズムは、表の形で要約できます。
テーブルタイプ|空の状態|空のときのしきい値|空でないときのしきい値
永久| <500行|変更数> = 500 |変更数> = 500 +(カーディナリティの20%)
KBが2000年を指していると考えていても、それは2012年までまだ当てはまります。
このシナリオを実行して、自分の目で確かめてください。
ステップ1
SET STATISTICS IO OFF;
GO
SET NOCOUNT ON;
GO
-- make sure the Include Actual Execution Plan is off!!!
IF OBJECT_ID('IDs') IS NOT NULL
DROP TABLE dbo.IDs;
CREATE TABLE IDs
(
ID tinyint NOT NULL
)
INSERT INTO IDs
SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7;
IF OBJECT_ID('TestStats') IS NOT NULL
DROP TABLE dbo.TestStats;
CREATE TABLE dbo.TestStats
(
ID tinyint NOT NULL,
Col1 int NOT NULL,
CONSTRAINT PK_TestStats PRIMARY KEY CLUSTERED (ID, col1)
);
DECLARE @id int = 1
DECLARE @i int = 1
WHILE @id <= 6
BEGIN
SET @i = 1
WHILE @i <= 20247
BEGIN
INSERT INTO dbo.TestStats VALUES(@id,@i);
SET @i = @i + 1
END
SET @id = @id + 1
END
-- so far so good!
SELECT ID, COUNT(*) AS RowCnt FROM dbo.TestStats GROUP BY ID;
DBCC SHOW_STATISTICS('TestStats',PK_TestStats) WITH HISTOGRAM;
これで、IDが1から6のテーブルがあり、各IDには20247行があります。これまでのところ、統計は良さそうです!
ステップ2
-- now insert another ID = 7 with 20247 rows
DECLARE @i int = 1;
WHILE @i <= 20247
BEGIN
INSERT INTO dbo.TestStats VALUES(7,@i);
SET @i = @i + 1
END
-- see the problem with the histogram?
SELECT ID, COUNT(*) FROM dbo.TestStats GROUP BY ID;
DBCC SHOW_STATISTICS('TestStats',PK_TestStats) WITH HISTOGRAM;
表とヒストグラムを見てください。実際のテーブルのID = 7は20247行ですが、自動更新がトリガーされなかったため、ヒストグラムには新しいデータが挿入されたことがわかりません。このテーブルの統計の自動更新をトリガーするには、(20247 * 6)* 0.2 + 500 = 24,796.4行を挿入する必要がある式に従ってください。
したがって、これらのクエリの計画を見ると、誤った見積もりが表示されます。
-- CTRL + M to include the Actual Execution plan
-- now, IF we run these queries, the Optimizer has no info about ID = 7
-- and the Estimates 1 because it cannot say 0.
SELECT ts.*
FROM dbo.TestStats ts
INNER JOIN dbo.IDs ON IDs.ID = ts.ID
WHERE IDs.ID = 1;
SELECT ts.*
FROM dbo.TestStats ts
INNER JOIN dbo.IDs ON IDs.ID = ts.ID
WHERE IDs.ID = 7;
クエリ#1:
クエリ#2:
オプティマイズは0行とは言えないため、1を表示するだけです。
ステップ3
-- now we manually update the stats
UPDATE STATISTICS dbo.TestStats WITH FULLSCAN;
-- check the histogram
DBCC SHOW_STATISTICS('TestStats',PK_TestStats) WITH HISTOGRAM;
-- rerun the queries
SELECT ts.*
FROM dbo.TestStats ts
INNER JOIN dbo.IDs ON IDs.ID = ts.ID
WHERE IDs.ID = 1;
SELECT ts.*
FROM dbo.TestStats ts
INNER JOIN dbo.IDs ON IDs.ID = ts.ID
WHERE IDs.ID = 7;
これで、ヒストグラムに欠落しているID 7が表示され、実行プランにも正しい見積もりが表示されます。
クエリ#1:
クエリ#2:
Q2)cacheIdの数が増えると、新しく挿入されたデータの見積もりが自然に改善されると思いますか?
はい、行の合計から20%+ 500のしきい値を超えるとすぐに。自動更新がトリガーされます。 STEP#1を再実行してこのシナリオを実行できますが、次のクエリを実行してSTEP#2を変更します。
-- now insert another ID = 7 with 20247 rows
DECLARE @i int = 1;
WHILE @i <= 20247
BEGIN
INSERT INTO dbo.TestStats VALUES(7,@i);
SET @i = @i + 1
END
-- see the problem with the histogram?
SELECT ID, COUNT(*) FROM dbo.TestStats GROUP BY ID;
DBCC SHOW_STATISTICS('TestStats',PK_TestStats) WITH HISTOGRAM;
GO
-- try to insert ID = 8 to trigger the auto update for the stats
DECLARE @i int = 1;
WHILE @i <= 4548
BEGIN
INSERT INTO dbo.TestStats VALUES(8,@i);
SET @i = @i + 1
END
-- no update yet
SELECT ID, COUNT(*) FROM dbo.TestStats GROUP BY ID;
DBCC SHOW_STATISTICS('TestStats',PK_TestStats) WITH HISTOGRAM;
しきい値が24,796.4-20247 = 4549.4であるため、まだ更新はありませんが、ID 8には4548行のみを挿入しました。次に、この1行を挿入して、ヒストグラムを再確認します。
-- this will trigger the update
INSERT INTO dbo.TestStats VALUES(8,4549);
-- double check
SELECT ID, COUNT(*) FROM dbo.TestStats GROUP BY ID;
DBCC SHOW_STATISTICS('TestStats',PK_TestStats) WITH HISTOGRAM;
Q3)新しいデータセットが挿入されるたびに統計を更新する必要なく(たとえば、偽のデータセットをはるかに大きなCacheId = 999999)。
SQL ServerでのAutostat(AUTO_UPDATE_STATISTICS)の動作の制御
ただし、テーブルが非常に大きくなると、古いしきい値(固定レート–行の20%が変更された)が高すぎて、Autostatプロセスが十分に頻繁にトリガーされない場合があります。これにより、パフォーマンスの問題が発生する可能性があります。 SQL Server 2008 R2 Service Pack 1以降のバージョンでは、このデフォルトの動作を変更できるトレースフラグ2371が導入されています。テーブルの行数が多いほど、統計の更新をトリガーするしきい値が低くなります。たとえば、トレースフラグがアクティブになっている場合、100万回の変更が発生すると、更新統計が10億行のテーブルでトリガーされます。トレースフラグがアクティブになっていない場合、10億のレコードを持つ同じテーブルで、統計の更新がトリガーされる前に2億の変更が必要になります。
これがあなたの理解に役立つことを願っています!かなり良い質問です!
Q3に対する1つの回答)
Q3)更新することなく推定を改善する(または1行の確実性を低くする)ための方法(ガチャ、トリックなど)はありますか?データの新しいセットが挿入されるたびに統計情報(たとえば、はるかに大きいCacheId = 999999で偽のデータセットを追加する).
結合では、IsNull()を使用して混乱を加え、最後に「最適化」を追加します。
select ... from ... join ...
where CacheId = IsNull(@cacheId, 0)
option (recompile, optimize for (@cacheId = 41274))
どちらも必要なようです。 Id 0は実際には存在しません。 「最適化」で使用されるID値は重要ではないようで、存在する必要もないようです。
補足:カスタム統計を削除して、CacheIdに新しいインデックスを追加しようとしましたが、暗黙的な統計は、最終的には更新行カウントのしきい値に関しては、明示的なカスタム統計と同じように動作しました。
2014-04-29を編集:
SQL Server 2014の改善された Cardinality Estimator では、「昇順キー」の推定値が改善されている可能性があります
Mark Storey-Smith コメントから、2005-SP1以降 Ascending keys のtraceon()ソリューションもあります。
2015-05-07を編集:
いくつかのケースはまだ1行を推定していました(sometimes)。 unknownの使用が役立つようで、IsNull()も削除できます。
select ... from ... join ...
where CacheId = @cacheId
option (recompile, optimize for (@cacheId = unknown))