不明なシナリオのすべての最適化でLIKE
演算子を使用する場合、レガシーCEと新しいCEの両方が9%の見積もりを使用するという印象を受けました(関連する統計が利用可能であり、クエリオプティマイザーが頼る必要がないと仮定します)選択性の推測に)。
クレジットデータベースに対して以下のクエリを実行すると、さまざまなCEでさまざまな見積もりが得られます。新しいCEでは、予想していた900行の見積もりを受け取ります。レガシーCEでは、241.416の見積もりを受け取り、この見積もりがどのように導出されるのかわかりません。誰かが光を当てることができますか?
-- New CE (Estimate = 900)
DECLARE @LastName VARCHAR(15) = 'BA%'
SELECT * FROM [Credit].[dbo].[member]
WHERE [lastname] LIKE @LastName;
-- Forcing Legacy CE (Estimate = 241.416)
DECLARE @LastName VARCHAR(15) = 'BA%'
SELECT * FROM [Credit].[dbo].[member]
WHERE [lastname] LIKE @LastName
OPTION (
QUERYTRACEON 9481,
QUERYTRACEON 9292,
QUERYTRACEON 9204,
QUERYTRACEON 3604
);
私のシナリオでは、既にクレジットデータベースを互換性レベル120に設定しているため、2番目のクエリでトレースフラグを使用してレガシーCEを強制し、クエリオプティマイザーによって使用/考慮される統計に関する情報も提供するのはなぜですか。 'lastname'の列統計が使用されているのを確認できますが、241.416の推定値がどのように導出されるのかまだわかりません。
私はオンラインで何も見つけることができませんでした このItzik Ben-Ganの記事 は、「未知のシナリオのすべての最適化でLIKE述語を使用する場合、レガシーCEと新しいCEの両方が9%の見積もりを使用する」と述べています。 。その投稿の情報は正しくないようです。
LIKE
の推測あなたの場合は以下に基づいています:
G
:標準の9%推測(_sqllang!x_Selectivity_Like
_)M
:6の因数(マジックナンバー)D
:整数に切り捨てられた平均データ長(統計から)(バイト単位)具体的には、_sqllang!CCardUtilSQL7::ProbLikeGuess
_は以下を使用します。
Selectivity (S) = G / M * LOG(D)
ノート:
D
が1〜2の場合、LOG(D)
項は省略されます。D
が1未満の場合(欠落またはNULL
統計を含む):D = FLOOR(0.5 * maximum column byte length)
この種の癖と複雑さは、元のCEの典型的なものです。
質問の例では、平均の長さは5です(_DBCC SHOW_STATISTICS
_からの5.6154は切り捨てられます)。
見積もり= 10,000 *(0.09/6 * LOG(5))= 241.416
その他の値の例:
D = Sの式を使用して推定 15 = 406.208 14 = 395.859 13 = 384.742 12 = 372.736 11 = 359.684 10 = 345.388 09 = 329.584 08 = 311.916 07 = 291.887 06 = 268.764 05 = 241.416 04 = 207.944 03 = 164.792 02 = 150.000(LOG未使用) 01 = 150.000(LOG未使用) 00 = 291.887(LOG 7)/ * FLOOR(0.5 * 15) [ラストネームはvarchar(15)なので15] */
_DECLARE
@CharLength integer = 5, -- Set length here
@Counter integer = 1;
CREATE TABLE #T (c1 varchar(15) NULL);
-- Add 10,000 rows
SET NOCOUNT ON;
SET STATISTICS XML OFF;
BEGIN TRANSACTION;
WHILE @Counter <= 10000
BEGIN
INSERT #T (c1) VALUES (REPLICATE('X', @CharLength));
SET @Counter = @Counter + 1;
END;
COMMIT TRANSACTION;
SET NOCOUNT OFF;
SET STATISTICS XML ON;
-- Test query
DECLARE @Like varchar(15);
SELECT * FROM #T AS T
WHERE T.c1 LIKE @Like;
DROP TABLE #T;
_
レガシーCEを使用してSQL Server 2014でテストしましたが、基数の見積もりとして9%も得られませんでした。オンラインで正確なものを見つけることができなかったので、いくつかのテストを行ったところ、試行したすべてのテストケースに適合するモデルが見つかりましたが、それが完全であるかどうかはわかりません。
私が見つけたモデルでは、推定値はテーブルの行数、フィルターされた列の統計の平均キー長、場合によってはフィルターされた列のデータ型の長さから導き出されます。推定には2つの異なる式が使用されます。
FLOOR(平均キー長)= 0の場合、推定式は列統計を無視し、データ型の長さに基づいて推定を作成します。私はVARCHAR(N)でのみテストしたので、NVARCHAR(N)には別の式がある可能性があります。 VARCHAR(N)の式は次のとおりです。
(推定行)=(表の行)*(-0.004869 + 0.032649 * log10(データ型の長さ))
これは非常にうまく適合していますが、完全に正確ではありません。
X軸はデータ型の長さ、y軸は100万行のテーブルの推定行数です。
クエリオプティマイザーは、列に統計がない場合、または列に平均キー長を1未満にするのに十分なNULL値がある場合、この式を使用します。
たとえば、VARCHAR(50)でフィルタリングされ、列統計がない150k行のテーブルがあるとします。行推定予測は次のとおりです。
150000 *(-0.004869 + 0.032649 * log10(50))= 7590.1行
それをテストするSQL:
CREATE TABLE X_CE_LIKE_TEST_1 (
STRING VARCHAR(50)
);
CREATE STATISTICS X_STAT_CE_LIKE_TEST_1 ON X_CE_LIKE_TEST_1 (STRING) WITH NORECOMPUTE;
WITH
L0 AS (SELECT 1 AS c UNION ALL SELECT 1),
L1 AS (SELECT 1 AS c FROM L0 A CROSS JOIN L0 B),
L2 AS (SELECT 1 AS c FROM L1 A CROSS JOIN L1 B),
L3 AS (SELECT 1 AS c FROM L2 A CROSS JOIN L2 B),
L4 AS (SELECT 1 AS c FROM L3 A CROSS JOIN L3 B CROSS JOIN L2 C),
NUMS AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS NUM FROM L4)
INSERT INTO X_CE_LIKE_TEST_1 WITH (TABLOCK) (STRING)
SELECT TOP (150000) 'ZZZZZ'
FROM NUMS
ORDER BY NUM;
DECLARE @LastName VARCHAR(15) = 'BA%'
SELECT * FROM X_CE_LIKE_TEST_1
WHERE STRING LIKE @LastName;
SQL Serverは、近い近い7242.47の行数を見積もります。
FLOOR(平均キー長)> = 1の場合、FLOOR(平均キー長)の値に基づく別の数式が使用されます。これが私が試した値のいくつかの表です:
1 1.5%
2 1.5%
3 1.64792%
4 2.07944%
5 2.41416%
6 2.68744%
7 2.91887%
8 3.11916%
9 3.29584%
10 3.45388%
FLOOR(平均キー長)<6の場合は、上記の表を使用してください。それ以外の場合は、次の方程式を使用します。
(推定行)=(表の行)*(-0.003381 + 0.034539 * log10(FLOOR(平均キー長)))
これは他のものよりも適合性が優れていますが、まだ完全に正確ではありません。
X軸は平均キー長であり、Y軸は100万行のテーブルの推定行数です。
別の例として、フィルターされた列の統計用に、平均キー長が5.5の行が10k行あるテーブルがあるとします。行の見積もりは次のようになります。
10000 * 0.241416 = 241.416行。
それをテストするSQL:
CREATE TABLE X_CE_LIKE_TEST_2 (
STRING VARCHAR(50)
);
WITH
L0 AS (SELECT 1 AS c UNION ALL SELECT 1),
L1 AS (SELECT 1 AS c FROM L0 A CROSS JOIN L0 B),
L2 AS (SELECT 1 AS c FROM L1 A CROSS JOIN L1 B),
L3 AS (SELECT 1 AS c FROM L2 A CROSS JOIN L2 B),
L4 AS (SELECT 1 AS c FROM L3 A CROSS JOIN L3 B CROSS JOIN L2 C),
NUMS AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS NUM FROM L4)
INSERT INTO X_CE_LIKE_TEST_2 WITH (TABLOCK) (STRING)
SELECT TOP (10000)
CASE
WHEN NUM % 2 = 1 THEN REPLICATE('Z', 5)
ELSE REPLICATE('Z', 6)
END
FROM NUMS
ORDER BY NUM;
CREATE STATISTICS X_STAT_CE_LIKE_TEST_2 ON X_CE_LIKE_TEST_2 (STRING)
WITH NORECOMPUTE, FULLSCAN;
DECLARE @LastName VARCHAR(15) = 'BA%'
SELECT * FROM X_CE_LIKE_TEST_2
WHERE STRING LIKE @LastName;
行の推定値は241.416で、これは質問の内容と一致します。表にない値を使用すると、エラーが発生します。
ここのモデルは完璧ではありませんが、一般的な動作をかなりよく示していると思います。