奇妙な密度はサンプリングされた統計をもたらす


8

NCインデックスは、サンプリングとフルスキャンで推定すると、まったく異なる統計分布を取得します。サンプリングされたものは、奇妙な密度ベクトルを持っています。その結果、実行計画が不十分になります。


非クラスター化インデックスでサポートされているnull以外のFK列を含む、約2,700万行のテーブルがあります。テーブルは主キーでクラスター化されます。どちらの列もvarcharです。

FKカラムのフルスキャン統計更新により、通常の密度ベクトルが得られます。

All density Average Length  Columns
6,181983E-08    45,99747    INSTANCEELEMENTID
3,615442E-08    95,26874    INSTANCEELEMENTID, ID

つまり、INSTANCELEMENTID結合する個別の行ごとに約1.7行を読み取ることが期待されています。

ヒストグラムの一般的なビンは次のようになります。

RANGE_HI_KEY    RANGE_ROWS  EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
FOOBAR          133053      10      71366               1,679318

ただし、サンプルの更新を行う場合(このテーブルのデフォルトのサンプル番号は230k行です)、奇妙なことが起こります。

4,773657E-06    45,99596    INSTANCEELEMENTID
3,702179E-08    95,30183    INSTANCEELEMENTID, ID

上の密度INSTANCEELEMENTIDは2桁大きくなりました。(ただし、両方のカラムの密度はかなり許容できる値に推定されています)。

ヒストグラムの典型的なビンは次のようになります。

RANGE_HI_KEY    RANGE_ROWS  EQ_ROWS     DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
FOOBAR          143870,4    766,2573    1247                115,3596
ZOTZOT          131560,7    1           969                 135,7092

これは完全に異なるディストリビューションです。注意INSTANCEELEMENTID関連する数が最も多いIDのは12を持っている、最も一般的な数はそれはいくつかのビンは、get EQ_ROWS = 1、これはビンの約10%に起こることも非常に奇妙だ1です。

これに寄与する可能性のある奇妙な行の「不運な」ドローはありません。

ヒストグラムを正しく読んでいますか?サンプリングでEQ_ROWS、DISTINCT_RANGE_ROWS、AVG_RANGE_ROWSが正しくスケーリングされていないように見えませんか?

テーブルは、私が知る限り、ゆがんでいません。私は自分で値を推定してサンプラーをエミュレートしようとしましたtablesample。これらの結果を通常の方法でカウントすると、サンプラーではなく、フルスキャンバージョンと一致する結果が得られます。

さらに、クラスター化されたインデックスでこの動作を再現できませんでした。


私はこれをこれに絞り込んで再現しました:

CREATE TABLE F_VAL (
    id varchar(100) primary key,
    num_l_val int not null
)

set nocount on

declare @rowlimit integer = 20000000;

これを確認するには、テーブルが十分に大きい必要があります。私はこれを見たことがuniqueidentiferありますvarchar(100)が、見ていませんint。

declare @i integer = 1;

declare @r float = rand()

while @i < @rowlimit
begin
set @r = rand()
insert f_val (id,num_l_val)
values (
   cast(@i as varchar(100)) + REPLICATE('f', 40 - len(@i)),
   case when @r > 0.8 then 4 when @r > 0.5 then 3 when @r > 0.4 then 2 else 1 end
)
  set @i = @i + 1

end

create table k_val (
 id int identity primary key,
 f_val varchar(100) not null,
)

insert into k_val(f_val)
select id from F_VAL
union all select id from f_val where num_l_val - 1 = 1
union all select id from f_val where num_l_val - 2 = 1
union all select id from f_val where num_l_val - 3 = 1
order by id

create nonclustered index IX_K_VAL_F_VAL  ON K_VAL (F_VAL)

update statistics K_VAL(IX_K_VAL_F_VAL) 
dbcc show_statistics (k_val,IX_k_VAL_F_VAL)

update statistics K_VAL(IX_K_VAL_F_VAL) WITH FULLSCAN
dbcc show_statistics (k_val,IX_k_VAL_F_VAL)

2つの統計を比較します。サンプリングされたものは、今では完全に異なる密度ベクトルを表しており、ヒストグラムのビンはオフになっています。テーブルが歪んでいないことに注意してください。

intデータ型として使用してもこれは発生しません。SQLServerは使用時にデータポイント全体を検査しませんvarcharか?

問題が拡大しているように見えることは言及する価値があります。サンプルレートを上げると役立ちます。

回答:


3

私がアクセスできる最大のデータベースの非クラスター化インデックスの一部で、同じ密度の問題が発生しました。最初に、ヒストグラムと密度計算について行ったいくつかの観察から始めます。

  • 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更新は一部のサンプル更新よりも速く完了することができます。


返信ありがとう、ジョー!これはバグまたは機能のギャップのようです。INTベースの値を使用している場合、この動作は発生しません。INTの場合、システムはより適切に機能し、実際の分布にはるかに近づく統計的分布の推定値が得られます。StatManは明らかにいくつかのスムージング/ヒューリスティックを行いますが、ヒストグラムを直接計算し、それと同じソースデータを使用して、自分ではるかに優れた結果を得ることができるのはかなり困惑しますtablesample

@JohanBenumEvensberget IMO INT列に対して異なる動作をすることはそれほど不合理ではありません。INTを使用すると、欠損値のドメインがはるかに限定されます。文字列の場合、実際には長さ制限まで何でもかまいません。良いヒストグラムが得られない場合、それは当惑する可能性がありますが、ほとんどの場合非常にうまく機能します。コードは秘密なので、期待どおりに機能しているかどうかはわかりません。この問題はMSで対処する必要があると思われる場合は、ここに投稿することを検討してください。connect.microsoft.com
— Joe Obbish
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.