web-dev-qa-db-ja.com

SQL Server 2016でのSUBSTRING()を含む述語の推定の変更?

SUBSTRING()または他の文字列関数を含む述語のカーディナリティの推定方法に関するSQL Server 2016の変更に関するドキュメントまたは調査はありますか?

私が尋ねている理由は、互換モード130でパフォーマンスが低下したクエリを調べていたためです。その理由は、SUBSTRING()への呼び出しを含むWHERE句に一致する行数の見積もりの​​変更に関連していました。クエリの書き換えで問題を修正しましたが、SQL Server 2016のこの領域の変更に関するドキュメントを誰かが知っているかどうか疑問に思っています。

デモコードは以下です。このテストケースでは推定値は非常に近くなっていますが、精度はデータによって異なります。

テストケースでは、互換性レベル120では、SQL Serverが推定にヒストグラムを使用しているように見えますが、互換性レベル130では、SQL Serverはテーブルの一致が固定10%であると想定しているようです。

CREATE DATABASE MyStringTestDB;
GO
USE MyStringTestDB;
GO
DROP TABLE IF EXISTS dbo.StringTest;
CREATE TABLE dbo.StringTest ( [TheString] varchar(15) );
GO
INSERT INTO dbo.StringTest
VALUES
( 'Y5_CLV' );
INSERT INTO dbo.StringTest
VALUES
( 'Y5_EG3' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_NE' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_PQT' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_T2V' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_TT4' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_ZKK' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_LW6' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_QO3' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_TZ7' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_UZZ' );

CREATE CLUSTERED INDEX IX_Clustered ON dbo.StringTest (TheString);

/* 
Uses fixed % for estimate; 1.1 rows estimated in this case.
    Plan for computation:
        CSelCalcFixedFilter (0.1) <----
            Selectivity: 0.1
*/
ALTER DATABASE MyStringTestDB SET compatibility_level = 130;
GO
SELECT * 
FROM dbo.StringTest 
WHERE SUBSTRING(TheString, 1, CHARINDEX('_',TheString) - 1) = 'ZZ'
OPTION (QUERYTRACEON 2363, QUERYTRACEON 3604);

/* 
Uses histogram to get estimate of 1
 CSelCalcPointPredsFreqBased <----
      Distinct value calculation:
          CDVCPlanLeaf
              0 Multi-Column Stats, 1 Single-Column Stats, 0 Guesses
      Individual selectivity calculations:
          (none)
    Loaded histogram for column QCOL: [DBA].[dbo].[StringTest].TheString from stats with id 1
*/
ALTER DATABASE MyStringTestDB SET compatibility_level = 120;
GO
SELECT * 
FROM dbo.StringTest 
WHERE SUBSTRING(TheString, 1, CHARINDEX('_',TheString) - 1) = 'ZZ'
OPTION (QUERYTRACEON 2363, QUERYTRACEON 3604);

/*
-- Simpler rewrite; works fine in both compat levels and gets better estimate.
SELECT * 
FROM dbo.StringTest 
WHERE TheString LIKE 'ZZ[_]%'
OPTION (QUERYTRACEON 2363, QUERYTRACEON 3604);
*/
13
James L

ドキュメントは知りません。私はこれを調べて、いくつかの観察をしましたが、コメントには長すぎます。

10%の推定値は常に低下するわけではありません。次の例を見てください。

_TRUNCATE TABLE dbo.StringTest

INSERT INTO dbo.StringTest
SELECT TOP (1000000) 'ZZ_' + LEFT(NEWID(), 12)
FROM   master..spt_values v1,
       master..spt_values v2;
_

質問のWHERE句。

_WHERE SUBSTRING(TheString, 1, CHARINDEX('_',TheString) - 1) = 'ZZ'
_

テーブルには100万行が含まれています。それらはすべて述語と一致します。互換性レベル130の下では、10%の推定で100,000の推定が得られます。 120未満では、推定行は1.03913です。

120の動作はヒストグラムを使用しますが、異なる行の数を取得するためだけです。私の場合の密度ベクトルは1.039131E-06を示しており、これにテーブルのカーディナリティーが乗算されて、推定行数が取得されます。すべての値は実際には異なりますが、すべて述語と一致します。

_query_optimizer_estimate_cardinality_拡張イベントをトレースすると、130未満では2つの異なる_<StatsCollection Name="CStCollFilter"_イベントがあることがわかります。最初の見積もりは100,000です。 2つ目はヒストグラムを読み込み、CSelCalcPointPredsFreqBased/DistinctCountCalculatorを使用して1.04の推定値を取得します。この2番目の結果は未使用のようです。

あなたが観察した動作は130で一貫して適用されていません。120が1行のメモリの付与に取り組んでいるため、これは130の推定値にとって明らかな勝利であると期待して_ORDER BY TheString_を追加しましたが、この小さな変更で十分です130ケースでも1.03913までの推定行。

OPTION (QUERYRULEOFF SelectToFilter)を追加すると、ソートに入る推定が100,000に戻りますが、メモリ許可は増加せず、ソートから出される推定は依然としてテーブルの個別の値に基づいています。

enter image description here

同様に、クエリが並列プランを取得するように並列処理のコストしきい値を微調整することで、130のケースでは、より低い推定に戻すのに十分でした。 _QUERYTRACEON 8757_を追加すると、推定値も低くなります。 10%の見積もりは簡単な計画でのみ保持されるようです。

提案された書き換え

_WHERE TheString LIKE 'ZZ[_]%'
_

両方よりはるかに優れた見積もりを示しています。これの出力は

_  CSelCalcTrieBased

      Column: QCOL: [MyStringTestDB].[dbo].[StringTest].TheString
_

それが使用したことを示す tries 。これに関する詳細は、すぐ上の文字列要約統計セクション here にあります。

ただし、元のクエリとは異なります。 ___の最初のインスタンスは、動的に検出されるのではなく、常に3番目の文字であると見なされるようになりました。

この仮定が元のクエリにハードコードされている場合

_ WHERE SUBSTRING(TheString, 1, 3) = 'ZZ_'
_

推定方法がCSelCalcHistogramComparison(INTERVAL)に変更され、推定された行が正確になります。

それを範囲に変換することができます

_WHERE TheString >=  'ZZ_' AND TheString < ???
_

そして、ヒストグラムを使用して、その範囲内の値を持つ行の数を推定します。

ただし、これはカーディナリティの推定にのみ適用されます。 LIKEは、実行時に範囲シークを使用できるため、推奨されます。 SUBSTRING(TheString, 1, 3)またはLEFT(TheString, 3)はできません。

8
Martin Smith