SQLステートメントが単純なパラメータ化を使用している場合、実行プランで提供される情報から予想されるよりも複雑です。 。矛盾する情報が頻繁に提供されることを考えると、経験豊富なSQLServerユーザーでさえこれを誤解する傾向があるのは当然のことです。
SQL Server 2019CU14でStackOverflow2010データベースを使用し、データベースの互換性を150に設定した例をいくつか見てみましょう。
まず、新しい非クラスター化インデックスが必要です:
CREATE INDEX [IX dbo.Users Reputation (DisplayName)] ON dbo.Users (Reputation) INCLUDE (DisplayName);
1。適用される単純なパラメータ化
この最初のクエリ例では、単純なパラメータ化を使用しています。 :
SELECT U.DisplayName FROM dbo.Users AS U WHERE U.Reputation = 999;
推定 (実行前)計画には、次のパラメーター化関連の要素があります。
推定計画パラメータ化プロパティ
@1に注目してください パラメータは、上部に表示されるクエリテキストを除くすべての場所に導入されます。
実際 (実行後)計画には次のものがあります:
実際の計画のパラメーター化プロパティ
プロパティウィンドウでParameterizedTextが失われていることに注意してください。 要素、パラメータの実行時の値に関する情報を取得します。パラメータ化されたクエリテキストがウィンドウの上部に「@1」で表示されるようになりました 「999」ではなく「」。
2。単純なパラメータ化は適用されません
この2番目の例はありません 単純なパラメータ化を使用する:
-- Projecting an extra column
SELECT
U.DisplayName,
U.CreationDate -- NEW
FROM dbo.Users AS U
WHERE
U.Reputation = 999; 推定 プランショー:
今回は、パラメータ@1 インデックスシークにありません ツールチップですが、パラメータ化されたテキストおよびその他のパラメータリスト要素は以前と同じです。
実際を見てみましょう 実行計画:
結果は、前にパラメータ化された実際のと同じです。 計画(現在はインデックスシークを除く) ツールチップには、パラメータ化されていない値「999」が表示されます。上部に表示されるクエリテキストは、@1を使用しています パラメータマーカー。プロパティウィンドウも@1を使用します パラメータの実行時の値を表示します。
クエリはパラメータ化されたステートメントではありません 反対のすべての証拠にもかかわらず。
3。パラメータ化に失敗しました
私の3番目の例もないです サーバーによってパラメーター化されたもの:
-- LOWER function used
SELECT
U.DisplayName,
LOWER(U.DisplayName)
FROM dbo.Users AS U
WHERE
U.Reputation = 999; 推定 計画は:
@1についての言及はありません パラメータは現在どこにでもあり、パラメータリスト プロパティウィンドウのセクションがありません。
実際 実行計画は同じなので、わざわざ表示することはありません。
4。並列パラメータ化プラン
実行プランで並列処理を使用するもう1つの例を示します。テストクエリの推定コストが低いということは、並列処理のコストしきい値を1に下げる必要があることを意味します。
EXECUTE sys.sp_configure
@configname = 'cost threshold for parallelism',
@configvalue = 1;
RECONFIGURE; 今回の例はもう少し複雑です:
SELECT
U.DisplayName
FROM dbo.Users AS U
WHERE
U.Reputation >= 5
AND U.DisplayName > N'ZZZ'
ORDER BY
U.Reputation DESC; 推定 実行計画は次のとおりです:
上部のクエリテキストはパラメータ化されていないままですが、他のすべてはパラメータ化されていません。現在、@1という2つのパラメータマーカーがあります および@2 、単純なパラメータ化 2つの適切なリテラル値が見つかりました。
実際 実行計画は例1のパターンに従います:
上部のクエリテキストがパラメータ化され、プロパティウィンドウに実行時パラメータ値が含まれるようになりました。この並行計画(あり 並べ替え 演算子)は、単純なパラメータ化を使用してサーバーによって確実にパラメータ化されます。 。
これまでに示したすべての動作には理由があり、さらにいくつかの理由があります。計画の編集について説明するときに、このシリーズの次のパートでこれらの多くを説明しようと思います。
その間、一般的なshowplan、特にSSMSの状況は理想的とは言えません。これまでSQLServerを使用してきた人々にとっては混乱を招きます。どのパラメータマーカーを信頼し、どのパラメータマーカーを無視しますか?
特定のステートメントに単純なパラメーター化が正常に適用されているかどうかを判断するための信頼できる方法がいくつかあります。
最も便利なクエリストアの1つから始めましょう。残念ながら、想像するほど簡単ではありません。
データベースコンテキストのクエリストア機能を有効にする必要があります ステートメントが実行され、OPERATION_MODEが実行される場所 READ_WRITEに設定する必要があります 、クエリストアが積極的にデータを収集できるようにします。
これらの条件を満たした後、実行後のshowplan出力には、 StatementParameterizationTypeなどの追加の属性が含まれます。 。名前が示すように、これにはステートメントに使用されるパラメーター化のタイプを説明するコードが含まれています。
プランのルートノードが選択されると、SSMSプロパティウィンドウに表示されます:
StatementParameterizationType
値はsys.query_store_queryに記載されています :
- 0 –なし
- 1 –ユーザー(明示的なパラメーター化)
- 2 –単純なパラメーター化
- 3 –強制的なパラメーター化
この有益な属性は、実際の場合にのみSSMSに表示されます。 推定時に計画が要求され、欠落している プランが選択されています。計画はキャッシュする必要があることを覚えておくことが重要です 。 推定をリクエストする SSMSのプランは、作成されたプランをキャッシュしません(SQL Server 2012以降)。
プランがキャッシュされると、 StatementParameterizationType sys.dm_exec_query_planなどの通常の場所に表示されます 。
query_parameterization_type_descなど、パラメータ化タイプがクエリストアに記録されている他の場所も信頼できます。 sys.query_store_queryの列 。
1つの重要な警告。クエリストアの場合OPERATION_MODE READ_ONLYに設定されています 、 StatementParameterizationType 属性は引き続きSSMSに入力されます実際 計画-しかし、それは常にゼロ —誤った印象を与えると、ステートメントはパラメーター化されていなかった可能性があります。
クエリストアを有効にして満足している場合は、読み取りと書き込みが可能であることを確認し、SSMSで実行後の計画のみを確認してください。これでうまくいきます。
例に示されているように、SSMSのグラフィカルなshowplanウィンドウの上部に表示されるクエリテキストは信頼できません。 ParameterListにも頼ることはできません プロパティに表示されます プランのルートノードが選択されたときのウィンドウ。 ParameterizedText 推定に表示される属性 計画だけも決定的なものではありません。
ただし、個々のプランオペレータに関連付けられているプロパティに依存することはできます。与えられた例は、これらがツールチップに存在することを示しています オペレーターにカーソルを合わせたとき。
@1のようなパラメータマーカーを含む述語 または@2 パラメータ化された計画を示します。パラメータを含む可能性が最も高い演算子は、インデックススキャンです。 、インデックスシーク 、およびフィルタ 。
パラメーターマーカーを使用した述語
番号付けが@1で始まる場合 、単純なパラメータ化を使用します 。強制的なパラメータ化は@0で始まります 。ここに記載されている番号付けスキームは、いつでも変更される可能性があることに注意してください。
それにもかかわらず、これは私が使用する方法です ほとんどの場合、プランがサーバー側のパラメーター化の対象であるかどうかを判断します。一般に、パラメータマーカーを含む述語について、計画を視覚的にすばやく簡単に確認できます。この方法は、両方のタイプの計画でも機能し、推定 および実際 。
ステートメントがパラメーター化されているかどうかを判断するために、プランキャッシュと関連するDMOをクエリする方法はいくつかあります。当然、これらのクエリはキャッシュ内のプランでのみ機能するため、ステートメントは完了するまで実行され、キャッシュされ、その後何らかの理由で削除されていない必要があります。
最も直接的なアプローチは、アドホックを探すことです。 関心のあるステートメントと完全に一致するSQLテキストを使用して計画します。 アドホック 計画はシェルになります ParameterizedPlanHandleを含む ステートメントがサーバーによってパラメーター化されている場合。次に、プランハンドルを使用して、準備済みを見つけます。 予定。 アドホック アドホックワークロードの最適化が有効になっていて、問題のステートメントが1回しか実行されていない場合、プランは存在しません。
このタイプの照会は、多くの場合、大量のXMLを細断処理し、プランキャッシュ全体を少なくとも1回スキャンすることになります。キャッシュ内のプランがバッチ全体をカバーしているため、コードを間違えることも簡単です。バッチには複数のステートメントが含まれる場合があり、各ステートメントはパラメーター化されている場合とされていない場合があります。すべてのDMOが同じ粒度(バッチまたはステートメント)で機能するわけではないため、スタックを簡単に解除できます。
関心のあるステートメントを、それらの個々のステートメントのみの計画フラグメントとともにリストする効率的な方法を以下に示します。
SELECT
StatementText =
SUBSTRING(T.[text],
1 + (QS.statement_start_offset / 2),
1 + ((QS.statement_end_offset -
QS.statement_start_offset) / 2)),
IsParameterized =
IIF(T.[text] LIKE N'(%',
'Yes',
'No'),
query_plan =
TRY_CONVERT(xml, P.query_plan)
FROM sys.dm_exec_query_stats AS QS
CROSS APPLY sys.dm_exec_sql_text (QS.[sql_handle]) AS T
CROSS APPLY sys.dm_exec_text_query_plan (
QS.plan_handle,
QS.statement_start_offset,
QS.statement_end_offset) AS P
WHERE
-- Statements of interest
T.[text] LIKE N'%DisplayName%Users%'
-- Exclude queries like this one
AND T.[text] NOT LIKE N'%sys.dm%'
ORDER BY
QS.last_execution_time ASC,
QS.statement_start_offset ASC; 説明のために、前の4つの例を含む単一のバッチを実行してみましょう。
ALTER DATABASE SCOPED CONFIGURATION
CLEAR PROCEDURE_CACHE;
GO
-- Example 1
SELECT U.DisplayName
FROM dbo.Users AS U
WHERE U.Reputation = 999;
-- Example 2
SELECT
U.DisplayName,
U.CreationDate
FROM dbo.Users AS U
WHERE
U.Reputation = 999;
-- Example 3
SELECT
U.DisplayName,
LOWER(U.DisplayName)
FROM dbo.Users AS U
WHERE
U.Reputation = 999;
-- Example 4
SELECT
U.DisplayName
FROM dbo.Users AS U
WHERE
U.Reputation >= 5
AND U.DisplayName > N'ZZZ'
ORDER BY
U.Reputation DESC;
GO DMOクエリの出力は次のとおりです。
DMOクエリ出力
これにより、例1と例4のみが正常にパラメーター化されたことが確認されます。
SQL統計パフォーマンスカウンターを使用して、両方の推定のパラメーター化アクティビティに関する詳細な洞察を得ることができます。 および実際 予定。使用されるカウンターはセッションごとにスコープが設定されていないため、正確な結果を得るには、他の同時アクティビティがないテストインスタンスを使用する必要があります。
sys.dm_exec_query_optimizer_infoからのデータでパラメーター化カウンター情報を補足します 些細な計画に関する統計も提供するDMO。
カウンター情報を読み取るステートメントがそれらのカウンター自体を変更しないように、ある程度の注意が必要です。いくつかの一時的なストアドプロシージャを作成することで、これに対処します。
CREATE PROCEDURE #TrivialPlans
AS
SET NOCOUNT ON;
SELECT
OI.[counter],
OI.occurrence
FROM sys.dm_exec_query_optimizer_info AS OI
WHERE
OI.[counter] = N'trivial plan';
GO
CREATE PROCEDURE #PerfCounters
AS
SET NOCOUNT ON;
SELECT
PC.[object_name],
PC.counter_name,
PC.cntr_value
FROM
sys.dm_os_performance_counters AS PC
WHERE
PC.counter_name LIKE N'%Param%'; 特定のステートメントをテストするためのスクリプトは、次のようになります。
ALTER DATABASE SCOPED CONFIGURATION
CLEAR PROCEDURE_CACHE;
GO
EXECUTE #PerfCounters;
EXECUTE #TrivialPlans;
GO
SET SHOWPLAN_XML ON;
GO
-- The statement(s) under test:
-- Example 3
SELECT
U.DisplayName,
LOWER(U.DisplayName)
FROM dbo.Users AS U
WHERE
U.Reputation = 999;
GO
SET SHOWPLAN_XML OFF;
GO
EXECUTE #TrivialPlans;
EXECUTE #PerfCounters;
SHOWPLAN_XMLにコメントします バッチアウトしてターゲットステートメントを実行し、実際を取得します 予定。 推定のためにそのままにしておきます 実行計画。
記述どおりにすべてを実行すると、次の結果が得られます。
例3をテストしたときに値が変更された場所を上で強調しました。
「トリビアルプラン」カウンターが1050から1051に増加したことは、テストステートメントでトリビアルプランが見つかったことを示しています。
単純なパラメーター化カウンターは、試行と失敗の両方で1増加し、SQL Serverがステートメントをパラメーター化しようとしたが、失敗したことを示しています。
このシリーズの次のパートでは、単純なパラメータ化の方法を説明することで、私たちが見た奇妙なことを説明します。 および簡単な計画 コンパイルプロセスと対話します。
並列処理のコストしきい値を変更した場合 例を実行するには、リセットすることを忘れないでください(私のものは50に設定されています):
EXECUTE sys.sp_configure
@configname = 'cost threshold for parallelism',
@configvalue = 50;
RECONFIGURE;