web-dev-qa-db-ja.com

Format vs Rightを使用してパディングを適用すると、推定行数が劇的に変化するのはなぜですか?

私は仕事でクエリに取り組んでいました、それは次のような左結合を持っていました

cast(cola as varchar) + '-' + right('000' + cast(colb as varchar), 3) = x

このクエリの実際の実行プランはかなり近く、推定269対実際の475でした。

右のパディングをformat(colb、 '000')を使用するように変更すると、行数が少なくとも400万と大幅に誤って推定され、クエリに10〜15倍の時間がかかります。

誤った見積もりが問題を引き起こす理由は理解していますが、Formatを使用すると見積もりの​​精度が低下する理由はわかりません。

2
jmoreno

ここで起こっていることがいくつかあります:

  1. パフォーマンスの低下(ロジック読み取りが大幅に増加したため)
  2. 推定行数の精度の低下

関係する要因は次のとおりです。

  1. VARCHARデータと比較したSQLServer照合順序のインデックス付きNVARCHAR列(注:このシナリオは照合順序に固有です:照合順序がWindows照合順序の場合、パフォーマンスの識別可能な低下はありません。詳細については、「-」を参照してください。 VARCHARタイプとNVARCHARタイプを混在させる場合のインデックスへの影響 ")
  2. 非決定論的組み込み関数またはSQLCLR関数、OR T-SQL UDF(決定論的としてマークされているかどうかに関係なく)
  3. 断片化されたインデックス(問題となったのは単に古い統計であると思っていましたが、WITH FULLSCANを使用しても、統計だけを更新しても効果はありません)

上記の3つの要因は、テストによって確認されています(以下を参照)。 3つの要因のうち2つは、簡単に修正できます。

  1. インデックス付きのNVARCHAR列を使用する場合は、VARCHAR値をVARCHARに変換します。OR列の照合順序をWindows照合順序に変更します。
  2. インデックスの完全なREBUILDを実行します。 ALTER INDEX ... REORGANIZE;またはUPDATE STATISTICS ... WITH FULLSCAN;を単独で実行しても、(少なくとも推定行数に関しては)役に立たないようです。
  3. (オプションで)決定論的な代替手段が利用可能かどうかを検討します(たとえば、CASE / CONVERT + RIGHTFORMATよりも効率的であり、同じ結果が得られる場合は、必ずCASE / CONVERT + RIGHT; FORMATはいくつかの気の利いたことを行うことができますが、それは不要ですパディング)。

また、優先順位にも注意してください。正確な推定行数があるのが理想的ですが、それらが十分に近い場合は問題ありません。つまり、実際のパフォーマンスが向上しない場合は、超正確な推定行数を取得するために余分な作業を行う必要はありません(特に、断片化のレベルによっては、非決定論的関数により正確な行の見積もり!)。一方、(比較される値の)データ型または照合順序を変更すると、顕著なプラスの影響があるため、努力する価値があります。次に、インデックスのREBUILDを実行すると、推定行数に十分に近づくことができます。


試験方法

これをテストするために、ローカルの一時テーブルにsys.all_objectsの「name」列を500万行入力し(そしてSQL_Latin1_General_CP1_CI_ASの照合順序を使用して)、文字列列に非クラスター化インデックスを作成し、さらに10万行をフラグメントに追加しました。インデックス。

VARCHARリテラルでフィルタリングし、次に同じ文字列リテラルで、大文字の「N」を前に付けてNVARCHARにしました。これにより、比較値のデータ型の問題が分離されました。

次に、同じリテラル値でフィルタリングしましたが、FORMATの呼び出しでラップしました。これにより、非決定論的関数の問題が分離されました。

関数の決定論の動作への影響を確認するために、渡された値を返すだけの2つのSQLCLR関数を作成しましたが、一方は決定論的で、もう一方はそうではありません。これにより、問題が決定論であり、関数で他に何も起こっていないことが明らかになります。 T-SQLでこれを行う同等の方法がないように思われるため、SQLCLRを使用しました。関数が決定論的であるとシステムでマークされている場合でも(WITH SCHEMABINDINGを使用してUDFを作成することにより)、動作は非決定論的関数の動作を反映します(これをテストしましたが、以下には含めませんでした)。

SET STATISTICS IO, TIME ON;を使用し、SSMSの[実際の実行プランを含める]オプションをオンにしました。

最初の一連のテストを実行した後、次のコマンドを実行しました。

EXEC (N'USE [tempdb]; UPDATE STATISTICS #Objects [IX_#Objects_Name] WITH FULLSCAN;');

テストを再実行しました。論理読み取りの改善は最小限であり、推定行数に変更はありません。

次に実行しました:

ALTER INDEX ALL ON #Objects REORGANIZE;

テストを再実行しました。推定行数に変更はありません。

次に実行しました:

ALTER INDEX ALL ON #Objects REBUILD;

そしてfinallyは、論理読み取りと推定行数の両方で改善が見られました。

次に、テーブルを削除し、照合順序としてLatin1_General_100_CI_AS_SCを使用してテーブルを再作成し、上記のようにテストを再実行しました。

テストコード

SQLCLRコード

次のコードを使用して、まったく同じことを行う2つのスカラー関数を作成しました。渡された値を返すだけです。 2つの関数の唯一の違いは、一方がIsDeterministic = trueとしてマークされ、もう一方がIsDeterministic = falseとしてマークされていることです。

using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

public class ScalarFunctions
{
    [return: SqlFacet(MaxSize = 4000)]
    [SqlFunction(IsDeterministic = true, IsPrecise = true)]
    public static SqlString PassThrough_Deterministic(
             [SqlFacet(MaxSize = 4000)] SqlString TheString)
    {
        return TheString;
    }

    [return: SqlFacet(MaxSize = 4000)]
    [SqlFunction(IsDeterministic = false, IsPrecise = true)]
    public static SqlString PassThrough_NonDeterministic(
             [SqlFacet(MaxSize = 4000)] SqlString TheString)
    {
        return TheString;
    }
}

テストセットアップ

-- DROP TABLE #Objects;
CREATE TABLE #Objects
(
  [ObjectID] INT IDENTITY(1, 1) NOT NULL PRIMARY KEY,
  [Name] VARCHAR(128) COLLATE SQL_Latin1_General_CP1_CI_AS
                               -- Latin1_General_100_CI_AS_SC
);


-- Insert 5 million rows:
INSERT INTO #Objects ([Name])
  SELECT TOP (5000000) ao.[name]
  FROM   [master].[sys].[all_objects] ao
  CROSS JOIN [master].[sys].[all_columns] ac;

-- Create the index:
CREATE INDEX [IX_#Objects_Name] ON #Objects ([Name]) WITH (FILLFACTOR = 100);

-- Insert another 100k rows to fragment index and reduce accuracy of the statistics:
INSERT INTO #Objects ([Name])
  SELECT TOP (100000) ao.[name]
  FROM master.sys.all_objects ao
  CROSS JOIN master.sys.all_columns ac;

テスト(および結果)

結果キー:

  • Set "(A)" = SQL Server Collat​​ion(SQL_Latin1_General_CP1_CI_AS
  • Set "(B)" = Windows Collat​​ion(Latin1_General_100_CI_AS_SC
  • 各結果のコメント:{REBUILDの前}/{REBUILDの後}
  • "CS + CS" =計算スカラー+コンスタントスキャン
SET STATISTICS IO, TIME ON;
-- Total rows matching filter criteria: 2203

SELECT [ObjectID] FROM #Objects WHERE [Name] = 'objects';
-- (A) logical reads 13 (est. rows: 2125.67) / 9 (2203.15) Index Seek
-- (B) logical reads 13 (est. rows: 2019.74) / 9 (2203.25) Index Seek

SELECT [ObjectID] FROM #Objects WHERE [Name] = N'objects';
-- (A) logical reads 25159 (est. rows: 2125.67) / 23158 (2203.15) Index SCAN
-- (B) logical reads 13 (est. rows: 2019.74) / 9 (2203.25) Index Seek + CS + CS


SELECT [ObjectID] FROM #Objects WHERE [Name] = FORMAT(0, N'objects');
-- (A) logical reads 25159 (est. rows: 2433.23) / 23158 (2406.8) Index SCAN
-- (B) logical reads 13 (est. rows: 2307.69) / 9 (2208.75) Index Seek + CS + CS


SELECT [ObjectID] FROM #Objects WHERE [Name] =
                                      dbo.PassThrough_Deterministic(N'objects');
-- (A) logical reads 25159 (est. rows: 2125.67) / 23158 (2203.15) Index SCAN
-- (B) logical reads 13 (est. rows: 2019.74) / 9 (2203.25) Index Seek + CS + CS

SELECT [ObjectID] FROM #Objects WHERE [Name] =
                                      dbo.PassThrough_NonDeterministic(N'objects');
-- (A) logical reads 25159 (est. rows: 2433.23) / 23158 (2406.8) Index SCAN
-- (B) logical reads 13 (est. rows: 2307.69) / 9 (2208.75) Index Seek + CS + CS


SET STATISTICS IO, TIME OFF;


EXEC (N'USE [tempdb]; UPDATE STATISTICS #Objects [IX_#Objects_Name] WITH FULLSCAN;');

-- re-run tests

ALTER INDEX ALL ON #Objects REORGANIZE;

-- re-run tests

ALTER INDEX ALL ON #Objects REBUILD;

-- re-run tests

2番目のバリエーション

  1. DROPテーブル
  2. windows照合順序を使用してテーブルを再作成します
  3. 上記の「テスト」セクションのすべてのテストを再実行します
2
Solomon Rutzky

FORMAT は、比較されたvarchar列よりも高い データ型の優先順位 を持つnvarcharを返します。不正確な行数の見積もりに加えて、比較されたvarchar列をnvarcharに暗黙的に変換すると、その列のインデックスを効率的に使用できなくなります。

FORMATの結果をvarcharにキャストしてみてください。

4
Dan Guzman

FORMAT(Transact-SQL) の備考セクションに

FORMAT関数は非決定的です。

したがって、クエリプランナーは、この関数の結果として何を期待するかについて混乱しています。おそらく、非決定論的な振る舞いは、中間結果のキャッシュなど、いくつかの最適化を適用することさえ妨げます。