20

編集: サンプルコードを更新し、参照用に完全なテーブルとビューの実装を提供しましたが、本質的な質問は変更されていません。

クエリを実行しようとしているデータベースにかなり複雑なビューがあります。WHERE 句を特定の外部キー値にハードコーディングしてビューから行のセットを取得しようとすると、ビューは最適な実行計画で非常に迅速に実行されます (インデックスが適切に使用されるなど)。

SELECT * 
FROM dbo.ViewOnBaseTable
WHERE ForeignKeyCol = 20

しかし、クエリにパラメーターを追加しようとすると、突然実行計画が崩れてしまいます。以下のクエリを実行すると、いたるところでシークではなくインデックス スキャンが行われ、クエリのパフォーマンスが非常に低下します。

DECLARE @ForeignKeyCol int = 20

SELECT * 
FROM dbo.ViewOnBaseTable
WHERE ForeignKeyCol = @ForeignKeyCol 

SQL Server 2008 R2 を使用しています。ここで何が得られますか?最適ではない計画を引き起こしているパラメーターの使用についてはどうですか? どんな助けでも大歓迎です。

参考までに、エラーが発生しているオブジェクト定義を次に示します。

CREATE TABLE [dbo].[BaseTable]
(
    [PrimaryKeyCol] [uniqueidentifier] PRIMARY KEY,
    [ForeignKeyCol] [int] NULL,
    [DataCol] [binary](1000) NOT NULL
)

CREATE NONCLUSTERED INDEX [IX_BaseTable_ForeignKeyCol] ON [dbo].[BaseTable]
(
    [ForeignKeyCol] ASC
)

CREATE VIEW [dbo].[ViewOnBaseTable]
AS
SELECT
    PrimaryKeyCol,
    ForeignKeyCol,
    DENSE_RANK() OVER (PARTITION BY ForeignKeyCol ORDER BY PrimaryKeyCol) AS ForeignKeyRank,
    DataCol
FROM
    dbo.BaseTable

ウィンドウ関数が問題であることは確かですが、ウィンドウ関数がパーティション分割している単一の値でクエリをフィルター処理しているため、オプティマイザーが最初にフィルター処理してからウィンドウ関数を実行することが期待されます。これは、ハードコーディングされた例では行われますが、パラメータ化された例では行われません。以下は、2 つのクエリ プランです。上の計画は善で、下の計画は悪い。

クエリ実行プラン

4

3 に答える 3

24

使用する場合OPTION (RECOMPILE)は、実行前 (「推定」) の計画ではなく、実行後 (「実際の」) 計画を確認してください。一部の最適化は、実行時にのみ適用されます。

DECLARE @ForeignKeyCol int = 20;

SELECT ForeignKeyCol, ForeignKeyRank
FROM dbo.ViewOnBaseTable
WHERE ForeignKeyCol = @ForeignKeyCol
OPTION (RECOMPILE);

実行前の計画:

実行前計画

実行後の計画:

実行後の計画

SQL Server 2012 ビルド 11.0.3339 および SQL Server 2008 R2 ビルド 10.50.4270 でテスト済み

背景と制限事項

ウィンドウ関数が SQL Server 2005 に追加されたとき、オプティマイザには、これらの新しいシーケンス プロジェクションを超えて選択をプッシュする方法がありませんでした。これによりパフォーマンスの問題が発生するいくつかの一般的なシナリオに対処するために、SQL Server 2008 には新しい単純化ルールが追加されましたSelOnSeqPrj。これにより、値が定数である場合に適切な選択をプッシュできます。この定数は、クエリ テキスト内のリテラル、または を介し​​て取得されたパラメータのスニッフィング値である可能性がありますOPTION (RECOMPILE)NULLsクエリがこれを確認する必要がある場合がありますが、特に問題はありませんANSI_NULLS OFF。私の知る限り、単純化を定数値のみに適用することは実装上の制限です。変数を扱うように拡張できない特別な理由はありません。私の記憶では、SelOnSeqPrjルールは、最も一般的に見られるパフォーマンスの問題に対処します。

パラメータ化

クエリが正常に自動パラメータ化された場合、SelOnSeqPrjルールは適用されません。クエリが SSMS で自動パラメーター化されたかどうかを判断する信頼できる方法はありません。自動パラメーターが試行されたことを示すだけです。明確にするために、 のようなプレースホルダーの存在は、自動パラメーター化が試行されたことを示しているだけです。準備済みプランが再利用のためにキャッシュされたかどうかを確認する信頼できる方法は、「パラメーター化されたプラン ハンドル」がアドホック プランと準備済みプランの間のリンクを提供するプラン キャッシュを調べることです。[@0]

たとえば、次のクエリは、SSMS で自動パラメーター化されているようです。

SELECT *
FROM dbo.ViewOnBaseTable
WHERE ForeignKeyCol = 20;

しかし、プラン キャッシュは別の方法で示しています。

WITH XMLNAMESPACES
(
    DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan'
)
SELECT
    parameterized_plan_handle =
        deqp.query_plan.value('(//StmtSimple)[1]/@ParameterizedPlanHandle', 'nvarchar(64)'), 
    parameterized_text =
        deqp.query_plan.value('(//StmtSimple)[1]/@ParameterizedText', 'nvarchar(max)'),
    decp.cacheobjtype,
    decp.objtype,
    decp.plan_handle
FROM sys.dm_exec_cached_plans AS decp
CROSS APPLY sys.dm_exec_sql_text(decp.plan_handle) AS dest
CROSS APPLY sys.dm_exec_query_plan(decp.plan_handle) AS deqp
WHERE
    dest.[text] LIKE N'%ViewOnBaseTable%'
    AND dest.[text] NOT LIKE N'%dm_exec_cached_plans%';

アドホック プランのキャッシュ エントリ

強制パラメーター化のデータベース オプションが有効になっている場合、最適化が適用されないパラメーター化された結果が得られます。

ALTER DATABASE Sandpit SET PARAMETERIZATION FORCED;
DBCC FREEPROCCACHE;

SELECT *
FROM dbo.ViewOnBaseTable
WHERE ForeignKeyCol = 20;

強制パラメータ化計画

プラン キャッシュ クエリは、パラメーター化されたプラン ハンドルによってリンクされた、パラメーター化されたキャッシュされたプランを表示するようになりました。

パラメーター化されたプラン キャッシュ

回避策

可能であれば、ビューをインラインのテーブル値関数として書き直すことをお勧めします。これにより、(必要に応じて) 選択の意図した位置をより明確にすることができます。

CREATE FUNCTION dbo.ParameterizedViewOnBaseTable
    (@ForeignKeyCol integer)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
    SELECT
        bt.PrimaryKeyCol,
        bt.ForeignKeyCol,
        ForeignKeyRank = DENSE_RANK() OVER (
            PARTITION BY bt.ForeignKeyCol 
            ORDER BY bt.PrimaryKeyCol),
        bt.DataCol
    FROM dbo.BaseTable AS bt
    WHERE
        bt.ForeignKeyCol = @ForeignKeyCol;

クエリは次のようになります。

DECLARE @ForeignKeyCol integer = 20;
SELECT pvobt.*
FROM dbo.ParameterizedViewOnBaseTable(@ForeignKeyCol) AS pvobt;

実行計画では:

機能計画

于 2013-03-08T21:55:56.213 に答える
1

いつでも CROSS APPLY の方法を使用できます。

ALTER VIEW [dbo].[ViewOnBaseTable]
AS
SELECT
    PrimaryKeyCol,
    ForeignKeyCol,
    ForeignKeyRank,
    DataCol
FROM (
    SELECT DISTINCT
        ForeignKeyCol
    FROM dbo.BaseTable
) AS Src
CROSS APPLY (
    SELECT
        PrimaryKeyCol,
        DENSE_RANK() OVER (ORDER BY PrimaryKeyCol) AS ForeignKeyRank,
        DataCol
    FROM dbo.BaseTable AS B
    WHERE B.ForeignKeyCol = Src.ForeignKeyCol
) AS X
于 2013-03-11T15:49:13.970 に答える
-2

この特定のケースでは、パラメーターとテーブルの間のデータ型が正確に一致しないため、SQL Server が sargable 操作ではない暗黙的な変換を行う必要があるためだと思います。

テーブルのデータ型を確認し、パラメーターを同じ型にしてください。または、クエリの外で自分自身をキャストします。

于 2012-11-29T22:05:55.050 に答える