web-dev-qa-db-ja.com

新しく挿入されたデータの結合で1行の行推定を改善する方法

テーブルの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が必要だとは思わない。必要に応じて特定の質問に答えることができます! ]

2
crokusek

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:

Query #1:

クエリ#2:

Query #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:

Query #1

クエリ#2:

Query #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億の変更が必要になります。

これがあなたの理解に役立つことを願っています!かなり良い質問です!

5
DenisT

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))
1
crokusek