SQL Serverが統計を使用してレコード数を推定する方法について、誰かが私を助けてくれることを願っています。
テストスクリプト
USE [tempdb]
GO
CREATE TABLE t1
(
a INT NOT NULL,
b INT NOT NULL,
c INT CHECK (c between 1 and 50),
CONSTRAINT pk_a primary key(a)
);
GO
INSERT INTO t1(a,b,c)
SELECT number, number%1000+1, number%50+1
FROM master..spt_values
WHERE type = 'P' AND number BETWEEN 1 and 1000;
GO
CREATE STATISTICS s_b ON t1(b);
CREATE STATISTICS s_c ON t1(c);
GO
サンプルクエリ
DECLARE @c INT=300
SELECT * FROM t1 WHERE b>@c
SELECT * FROM t1 WHERE b>300
クエリ1
推定行数300
クエリ2
推定行数700
質問
最初のクエリは Parameter Embedding Optimization の恩恵を受けないため、クエリは異なる推定値を示します。オプティマイザの観点からは、クエリも次のようになります。
SELECT * FROM t1 WHERE b>???;
単純なクエリでカーディナリティの見積もりがどのように機能するかを理解するために、見積もりを作成するためにクエリオプティマイザーをプログラムする必要がある場合にどうするかについて考えることがあります。多くの場合、それらの考えはSQL Serverの動作と完全には一致しませんが、オプティマイザはほとんどの場合不完全または限られた情報を処理していることを覚えておくと役立ちます。最初のクエリに戻りましょう。
DECLARE @c INT=300;
SELECT * FROM t1 WHERE b>@c;
RECOMPILE
ヒントがないと、オプティマイザは@c
ローカル変数の値を認識しません。適切な見積もりを行うのに十分な情報はここにはありませんが、オプティマイザはすべてのクエリの見積もりを作成する必要があります。では、オプティマイザのプログラマは何ができるでしょうか?列の値の分布に基づいて推測を行う高度なアルゴリズムは実装可能だと思いますが、Microsoftは未知の不等式に対して30%のハードコードされた推測を使用しています。これは、ローカル変数の値を変更することで確認できます。
DECLARE @c INT=999999999;
SELECT * FROM t1 WHERE b>@c;
推定値はまだ300行です。推定値はクエリ間で一貫している必要はないことに注意してください。次のクエリにも、1000-300 = 700ではなく、300行の推定があります。
DECLARE @c INT=300;
SELECT * FROM t1 WHERE NOT (b>@c);
概念的には、次のように書き直すことができます
DECLARE @c INT=300;
SELECT * FROM t1 WHERE b<=@c;
同じハードコードされた30%の見積もりがあります。
2番目のクエリはローカル変数を使用しないため、オプティマイザーは列に作成した統計オブジェクトを直接使用して、推定を支援できます。ヒストグラムに含めることができるのは最大201ステップであり、テーブルには1000の異なる値があるため、オプティマイザが完全な情報を持たないフィルタ値があります。それらのケースでは、推測をさらに行う必要があります。 b
列のヒストグラムを表示する方法は次のとおりです。
DBCC SHOW_STATISTICS ('t1', 's_b') WITH HISTOGRAM;
私のマシンでは、300がヒストグラムのいずれかのステップの高い値であることで幸運になりました。
さまざまな列の説明は documentation にあります。今後は、それらの意味をすべて理解していると想定します。クエリはb > 300
を含む行を要求するため、RANGE_HI_KEY
が300のヒストグラムは、RANGE_HI_KEY
が小さいすべてのステップとは無関係です。このクエリでは、ヒストグラムの残りの133ステップのRANGE_ROWS
値とEQ_ROWS
値をすべて合計するようにオプティマイザをプログラムします。これらの列の合計は700になり、これはSQL Serverの見積もりです。
他のフィルター値では正確な結果が得られない場合があります。たとえば、次の2つのクエリのカーディナリティ推定値はどちらも704行です。
SELECT * FROM t1 WHERE b>293; -- returns 707 rows
SELECT * FROM t1 WHERE b>299; -- returns 701 rows
両方の見積もりは非常に近いですが、正確ではありません。ヒストグラムには、これらの値を正確に推定するのに十分な情報が含まれていません。
最初のケースについては、Q&A: SQLサーバーが統計の自動作成をオフに設定してクエリプランを生成する方法 を参照してください。 variableを使用するため、未知の入力と見なされ、テーブルのカーディナリティの30%(1000行の30%)と推定されます。
2番目のケースでは、統計ヒストグラムを使用します。 Nelson Johnによる SQL Server:Part 2:All About SQL Server Statistics:Histogram でそれらについて学ぶことができます。
また、@c1
の現在の値に基づいて新しいプランを強制的にコンパイルすると、パラメーター化されたクエリの700行の推定値が得られます。例えば:
SELECT * FROM t1 WHERE b>@c OPTION (RECOMPILE);