web-dev-qa-db-ja.com

LEN()関数がSQL Server 2014のカーディナリティを大幅に過小評価するのはなぜですか?

文字列列と特定の長さの行をチェックする述語を持つテーブルがあります。 SQL Server 2014では、チェックしている長さに関係なく、1行の見積もりが表示されます。実際には数千または数百万の行があり、SQL Serverはこのテーブルをネストされたループの外側に配置することを選択しているため、これは非常に貧弱な計画を生み出しています。

SQL Server 2014のカーディナリティの推定値1.0003に対する説明はありますか?SQL Server 2012は31,622行を推定しますか?良い回避策はありますか?

これは問題の短い複製です:

-- Create a table with 1MM rows of dummy data
CREATE TABLE #customers (cust_nbr VARCHAR(10) NOT NULL)
GO

INSERT INTO #customers WITH (TABLOCK) (cust_nbr)
    SELECT TOP 1000000 
        CONVERT(VARCHAR(10),
        ROW_NUMBER() OVER (ORDER BY (SELECT NULL))) AS cust_nbr
    FROM master..spt_values v1
    CROSS JOIN master..spt_values v2
GO

-- Looking for string of a certain length.
-- While both CEs yield fairly poor estimates, the 2012 CE is much
-- more conservative (higher estimate) and therefore much more likely
-- to yield an okay plan rather than a drastically understimated loop join.
-- 2012: 31,622 rows estimated, 900K rows actual
-- 2014: 1 row estimated, 900K rows actual
SELECT COUNT(*)
FROM #customers
WHERE LEN(cust_nbr) = 6
OPTION (QUERYTRACEON 9481) -- Optionally, use 2012 CE
GO

これは、追加のテストを示すより完全なスクリプトです

SQL Server 2014 Cardinality Estimatorに関するホワイトペーパー も読みましたが、状況を明らかにするものは見つかりませんでした。

26
Geoff Patterson

レガシーCEの場合、行の3.16228%の見積もりであることがわかります。これは、column =リテラル述語に使用される「マジックナンバー」ヒューリスティックです(述語の構築に基づく他のヒューリスティックもありますが、LENレガシーCEの結果の列を囲むと、この推測フレームワークと一致します)。この例は、Joe Sackによる 統計がない場合の選択性推測 、およびIan Joseによる 定数-定数比較推定 の投稿で確認できます。

-- Legacy CE: 31622.8 rows
SELECT  COUNT(*)
FROM    #customers
WHERE   LEN(cust_nbr) = 6
OPTION  ( QUERYTRACEON 9481); -- Legacy CE
GO

新しいCEの動作については、これはオプティマイザから見えるようになりました(つまり、統計を使用できます)。以下の計算機の出力を見ることで、関連する統計の自動生成をポインターとして見ることができます。

-- New CE: 1.00007 rows
SELECT  COUNT(*)
FROM    #customers
WHERE   LEN(cust_nbr) = 6
OPTION  ( QUERYTRACEON 2312 ); -- New CE
GO

-- View New CE behavior with 2363 (for supported option use XEvents)
SELECT  COUNT(*)
FROM    #customers
WHERE   LEN(cust_nbr) = 6
OPTION  (QUERYTRACEON 2312, QUERYTRACEON 2363, QUERYTRACEON 3604, RECOMPILE); -- New CE
GO

/*
Loaded histogram for column QCOL:
[tempdb].[dbo].[#customers].cust_nbr from stats with id 2
Using ambient cardinality 1e+006 to combine distinct counts:
  999927

Combined distinct count: 999927
Selectivity: 1.00007e-006
Stats collection generated:
  CStCollFilter(ID=2, CARD=1.00007)
      CStCollBaseTable(ID=1, CARD=1e+006 TBL: #customers)

End selectivity computation
*/

EXEC tempdb..sp_helpstats '#customers';


--Check out AVG_RANGE_ROWS values (for example - plenty of ~ 1)
DBCC SHOW_STATISTICS('tempdb..#customers', '_WA_Sys_00000001_B0368087');
--That's my Stats name yours is subject to change

残念ながら、このロジックはLEN関数の効果に対して調整されていない個別の値の数の見積もりに依存しています。

可能な回避策

LENLIKEとして書き換えることで、両方のCEモデルでトライベースの推定を取得できます。

SELECT COUNT_BIG(*)
FROM #customers AS C
WHERE C.cust_nbr LIKE REPLICATE('_', 6);

LIKE plan


使用されるトレースフラグに関する情報:

  • 2363:ロードされている統計を含む多くの情報を表示します。
  • 3604:DBCCコマンドの出力をメッセージタブに出力します。
20
Zane

SQL 2012の基数推定値1.0003の説明はありますが、SQL 2012は31,622行を推定しますか?

@Zaneの answer はこの部分をかなりカバーしていると思います。

良い回避策はありますか?

LEN(cust_nbr)の非永続計算列を作成して、(オプションで)その計算列に非クラスター化インデックスを作成してみてください。これで正確な統計が得られます。

私はいくつかのテストを行い、ここに私が見つけたものがあります:

  • インデックスが定義されていない場合、統計は非永続計算列で自動作成されました。
  • 計算列に非クラスター化インデックスを追加しても効果がなかっただけでなく、実際にはパフォーマンスが少し低下しました。わずかに高いCPUと経過時間。わずかに高い推定コスト(価値があるものは何でも)。
  • 計算列をPERSISTED(インデックスなし)にすることは、他の2つのバリエーションよりも優れていました。推定行はより正確でした。 CPUと経過時間はより優れていました(行ごとに何も計算する必要がないため、予想どおり)。
  • たとえPERSISTEDであっても、(計算されているため)計算された列にフィルターインデックスまたはフィルター統計を作成できませんでした:-(
13
Solomon Rutzky