私がアクセスできる最大のデータベースの非クラスター化インデックスの一部で、同じ密度の問題が発生しました。最初に、ヒストグラムと密度計算について行ったいくつかの観察から始めます。
- SQL Serverは、テーブルの主キーを使用して、両方の列の密度について何かを推測できます。つまり、PKカラムを含む密度は通常非常に正確です。
- 統計の最初の列の密度計算は、ヒストグラムと一致しています。ヒストグラムがデータを適切にモデル化していない場合、密度がオフになっている可能性があります。
- ヒストグラムを作成するために、
StatMan
関数は欠落しているデータについて推論を行います。列のデータ型に応じて動作が変わる場合があります。
問題を調べる1つの方法として、10000行のテーブルから100行をサンプリングし、100個の異なる値を取得するとします。テーブルの残りのデータが何であるかは、10000個の一意の値があると推測されます。別の推測では、100個の異なる値がありますが、各値は100回繰り返されます。2番目の推測はあなたには不合理に思えるかもしれませんが、私はそれに同意します。しかし、サンプリングされたデータが不均一に分散して戻ってきた場合、2つのアプローチのバランスをどのようにとるのでしょうか。このStatMan
関数には、Microsoftがこのために開発したアルゴリズムのセットが含まれています。アルゴリズムは、すべてのデータ破壊とすべてのサンプルレベルに対して機能しない場合があります。
比較的簡単な例を見てみましょう。VARCHAR
テーブルのように列を使用して、同じ動作の一部を確認します。ただし、歪んだ値を1つだけテーブルに追加します。SQL Server 2016 SP1に対してテストしています。列に10万の一意の値を含む10万行から始めFK
ます。
DROP TABLE IF EXISTS X_STATS_SMALL;
CREATE TABLE X_STATS_SMALL (
ID VARCHAR(10) NOT NULL,
FK VARCHAR(10) NOT NULL,
PADDING VARCHAR(900) NOT NULL,
PRIMARY KEY (ID)
);
-- insert 100k rows
INSERT INTO X_STATS_SMALL WITH (TABLOCK)
SELECT N, N, REPLICATE('Z', 900)
FROM dbo.GetNums(100000);
CREATE INDEX IX_X_STATS_SMALL ON X_STATS_SMALL (FK);
-- get sampled stats
UPDATE STATISTICS X_STATS_SMALL IX_X_STATS_SMALL;
統計からのいくつかのサンプルを以下に示します。
╔═════════════╦════════════════╦═════════╗
║ All density ║ Average Length ║ Columns ║
╠═════════════╬════════════════╬═════════╣
║ 1.00001E-05 ║ 4.888205 ║ FK ║
║ 1.00001E-05 ║ 9.77641 ║ FK, ID ║
╚═════════════╩════════════════╩═════════╝
╔══════════════╦════════════╦═════════╦═════════════════════╦════════════════╗
║ RANGE_HI_KEY ║ RANGE_ROWS ║ EQ_ROWS ║ DISTINCT_RANGE_ROWS ║ AVG_RANGE_ROWS ║
╠══════════════╬════════════╬═════════╬═════════════════════╬════════════════╣
║ 1005 ║ 0 ║ 1 ║ 0 ║ 1 ║
║ 10648 ║ 665.0898 ║ 1 ║ 664 ║ 1.002173 ║
║ 10968 ║ 431.6008 ║ 1 ║ 432 ║ 1 ║
║ 11182 ║ 290.0924 ║ 1 ║ 290 ║ 1 ║
║ 1207 ║ 445.7517 ║ 1 ║ 446 ║ 1 ║
║ ... ║ ... ║ ... ║ ... ║ ... ║
║ 99989 ║ 318.3941 ║ 1 ║ 318 ║ 1 ║
╚══════════════╩════════════╩═════════╩═════════════════════╩════════════════╝
行ごとに1つの一意の値を持つ均等に分散されたデータの場合、VARCHAR
ヒストグラム列と14294行のサンプルサイズでも、正確な密度が得られます。
次に、歪んだ値を追加して、統計を再度更新します。
-- add 70k rows with a FK value of '35000'
INSERT INTO X_STATS_SMALL WITH (TABLOCK)
SELECT N + 100000 , '35000', REPLICATE('Z', 900)
FROM dbo.GetNums(70000);
UPDATE STATISTICS X_STATS_SMALL IX_X_STATS_SMALL;
17010行のサンプルサイズでは、最初の列の密度は本来あるべき値よりも小さくなります。
╔══════════════╦════════════════╦═════════╗
║ All density ║ Average Length ║ Columns ║
╠══════════════╬════════════════╬═════════╣
║ 6.811061E-05 ║ 4.935802 ║ FK ║
║ 5.882353E-06 ║ 10.28007 ║ FK, ID ║
╚══════════════╩════════════════╩═════════╝
╔══════════════╦════════════╦══════════╦═════════════════════╦════════════════╗
║ RANGE_HI_KEY ║ RANGE_ROWS ║ EQ_ROWS ║ DISTINCT_RANGE_ROWS ║ AVG_RANGE_ROWS ║
╠══════════════╬════════════╬══════════╬═════════════════════╬════════════════╣
║ 10039 ║ 0 ║ 1 ║ 0 ║ 1 ║
║ 10978 ║ 956.9945 ║ 1 ║ 138 ║ 6.954391 ║
║ 11472 ║ 621.0283 ║ 1 ║ 89 ║ 6.941863 ║
║ 1179 ║ 315.6046 ║ 1 ║ 46 ║ 6.907561 ║
║ 11909 ║ 91.62713 ║ 1 ║ 14 ║ 6.74198 ║
║ ... ║ ... ║ ... ║ ... ║ ... ║
║ 35000 ║ 376.6893 ║ 69195.05 ║ 54 ║ 6.918834 ║
║ ... ║ ... ║ ... ║ ... ║ ... ║
║ 99966 ║ 325.7854 ║ 1 ║ 47 ║ 6.909731 ║
╚══════════════╩════════════╩══════════╩═════════════════════╩════════════════╝
AVG_RANGE_ROWS
サンプルが重複する値を見つけることができなかったキーのバケットでも、が6.9前後のすべてのステップでかなり均一であることは驚くべきことです。なぜなのかわかりません。最も可能性の高い説明は、失われたページを推測するために使用されるアルゴリズムが、このデータ分布とサンプルサイズでうまく機能しないことです。
前述のように、ヒストグラムを使用してFK列の密度を計算することが可能です。DISTINCT_RANGE_ROWS
すべてのステップの値の合計は14497です。179のヒストグラムステップがあるため、密度は約1 /(179 + 14497)= 0.00006813845で、レポートされた値にかなり近いはずです。
より大きなテーブルでテストすると、テーブルが大きくなるにつれて問題がどのように悪化するかを示すことができます。今回は100万行から始めます。
DROP TABLE IF EXISTS X_STATS_LARGE;
CREATE TABLE X_STATS_LARGE (
ID VARCHAR(10) NOT NULL,
FK VARCHAR(10) NOT NULL,
PADDING VARCHAR(900) NOT NULL,
PRIMARY KEY (ID));
INSERT INTO X_STATS_LARGE WITH (TABLOCK)
SELECT N, N, REPLICATE('Z', 900)
FROM dbo.Getnums(1000000);
CREATE INDEX IX_X_STATS_LARGE ON X_STATS_LARGE (FK);
-- get sampled stats
UPDATE STATISTICS X_STATS_LARGE IX_X_STATS_LARGE;
統計オブジェクトはまだ興味深いものではありません。の密度FK
は1.025289E-06であり、正確な(1.0E-06)に近いです。
次に、歪んだ値を追加して、統計を再度更新します。
INSERT INTO X_STATS_LARGE WITH (TABLOCK)
SELECT N + 1000000 , '350000', REPLICATE('Z', 900)
FROM dbo.Getnums(700000);
UPDATE STATISTICS X_STATS_LARGE IX_X_STATS_LARGE;
45627行のサンプルサイズでは、最初の列の密度は以前よりも悪くなります。
╔══════════════╦════════════════╦═════════╗
║ All density ║ Average Length ║ Columns ║
╠══════════════╬════════════════╬═════════╣
║ 2.60051E-05 ║ 5.93563 ║ FK ║
║ 5.932542E-07 ║ 12.28485 ║ FK, ID ║
╚══════════════╩════════════════╩═════════╝
╔══════════════╦════════════╦═════════╦═════════════════════╦════════════════╗
║ RANGE_HI_KEY ║ RANGE_ROWS ║ EQ_ROWS ║ DISTINCT_RANGE_ROWS ║ AVG_RANGE_ROWS ║
╠══════════════╬════════════╬═════════╬═════════════════════╬════════════════╣
║ 100023 ║ 0 ║ 1 ║ 0 ║ 1 ║
║ 107142 ║ 8008.354 ║ 1 ║ 306 ║ 26.17787 ║
║ 110529 ║ 4361.357 ║ 1 ║ 168 ║ 26.02392 ║
║ 114558 ║ 3722.193 ║ 1 ║ 143 ║ 26.01217 ║
║ 116696 ║ 2556.658 ║ 1 ║ 98 ║ 25.97568 ║
║ ... ║ ... ║ ... ║ ... ║ ... ║
║ 350000 ║ 5000.522 ║ 700435 ║ 192 ║ 26.03268 ║
║ ... ║ ... ║ ... ║ ... ║ ... ║
║ 999956 ║ 2406.266 ║ 1 ║ 93 ║ 25.96841 ║
╚══════════════╩════════════╩═════════╩═════════════════════╩════════════════╝
AVG_RANGE_ROWS
興味深いことに、サンプルサイズを170100行(他のテーブルの10倍)に変更すると、平均値AVG_RANGE_ROWS
は6.9前後になります。テーブルが大きくなると、SQL Serverはより小さなサンプルサイズを選択します。つまり、テーブル内のページのパーセンテージを推測する必要があります。これは、特定の種類のデータスキューの統計問題を誇張する可能性があります。
結論として、SQL Serverは次のように密度を計算しないことを覚えておくことが重要です。
SELECT COUNT(DISTINCT FK) * 1700000. / COUNT(*) -- 1071198.9 distinct values for one run
FROM X_STATS_LARGE TABLESAMPLE (45627 ROWS);
一部のデータ分布では、これは非常に正確になります。代わりに、文書化されていないアルゴリズムを使用します。質問で、データは歪んでいないと述べましたが、INSTANCEELEMENTID
関連付けられたIDの数が最も多い値は12であり、最も一般的な数は1ですStatman
。
その時点では、サンプルレートの高い統計情報を収集する以外にできることはありません。1つの一般的な戦略は、FULLSCAN
およびで統計を収集することNORECOMPUTE
です。データの変化率にとって意味のある間隔で、ジョブを使用して統計を更新できます。私の経験でFULLSCAN
は、特にインデックスに対して、更新はほとんどの人が考えるほど悪くはありません。SQL Serverは、テーブル全体ではなくインデックス全体をスキャンすることができます(インデックスのない列に対する行ストアテーブルの場合と同様)。さらに、SQL Serer 2014では、FULLSCAN
統計の更新のみが並行して行われるため、FULLSCAN
更新は一部のサンプル更新よりも速く完了することができます。
tablesample