私は仕事でクエリに取り組んでいました、それは次のような左結合を持っていました
cast(cola as varchar) + '-' + right('000' + cast(colb as varchar), 3) = x
このクエリの実際の実行プランはかなり近く、推定269対実際の475でした。
右のパディングをformat(colb、 '000')を使用するように変更すると、行数が少なくとも400万と大幅に誤って推定され、クエリに10〜15倍の時間がかかります。
誤った見積もりが問題を引き起こす理由は理解していますが、Formatを使用すると見積もりの精度が低下する理由はわかりません。
ここで起こっていることがいくつかあります:
関係する要因は次のとおりです。
VARCHAR
データと比較したSQLServer照合順序のインデックス付きNVARCHAR
列(注:このシナリオは照合順序に固有です:照合順序がWindows照合順序の場合、パフォーマンスの識別可能な低下はありません。詳細については、「-」を参照してください。 VARCHARタイプとNVARCHARタイプを混在させる場合のインデックスへの影響 ")WITH FULLSCAN
を使用しても、統計だけを更新しても効果はありません)上記の3つの要因は、テストによって確認されています(以下を参照)。 3つの要因のうち2つは、簡単に修正できます。
NVARCHAR
列を使用する場合は、VARCHAR
値をVARCHAR
に変換します。OR列の照合順序をWindows照合順序に変更します。REBUILD
を実行します。 ALTER INDEX ... REORGANIZE;
またはUPDATE STATISTICS ... WITH FULLSCAN;
を単独で実行しても、(少なくとも推定行数に関しては)役に立たないようです。CASE / CONVERT
+ RIGHT
がFORMAT
よりも効率的であり、同じ結果が得られる場合は、必ず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;
テスト(および結果)
結果キー:
SQL_Latin1_General_CP1_CI_AS
)Latin1_General_100_CI_AS_SC
)REBUILD
の前}/{REBUILD
の後}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番目のバリエーション
FORMAT(Transact-SQL) の備考セクションに
FORMAT関数は非決定的です。
したがって、クエリプランナーは、この関数の結果として何を期待するかについて混乱しています。おそらく、非決定論的な振る舞いは、中間結果のキャッシュなど、いくつかの最適化を適用することさえ妨げます。