Optimizing Execution Plans for Parameterized T-SQL Queries Containing Window Functions

前端 未结 3 808
逝去的感伤
逝去的感伤 2020-12-05 18:33

EDIT: I\'ve updated the example code and provided complete table and view implementations for reference, but the essential question remains unchanged.

相关标签:
3条回答
  • 2020-12-05 19:13

    You could always go the CROSS APPLY way.

    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
    
    0 讨论(0)
  • 2020-12-05 19:26

    When using OPTION (RECOMPILE) be sure to look at the post-execution ('actual') plan rather than the pre-execution ('estimated') one. Some optimizations are only applied when execution occurs:

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

    Pre-execution plan:

    Pre-execution plan

    Post-execution plan:

    Post-execution plan

    Tested on SQL Server 2012 build 11.0.3339 and SQL Server 2008 R2 build 10.50.4270

    Background & limitations

    When windowing functions were added in SQL Server 2005, the optimizer had no way to push selections past these new sequence projections. To address some common scenarios where this caused performance problems, SQL Server 2008 added a new simplification rule, SelOnSeqPrj, which allows suitable selections to be pushed where the value is a constant. This constant may be a literal in the query text, or the sniffed value of a parameter obtained via OPTION (RECOMPILE). There is no particular problem with NULLs though the query may need to have ANSI_NULLS OFF to see this. As far as I know, applying the simplification to constant values only is an implementation limitation; there is no particular reason it could not be extended to work with variables. My recollection is that the SelOnSeqPrj rule addresssed the most commonly seen performance problems.

    Parameterization

    The SelOnSeqPrj rule is not applied when a query is successfully auto-parameterized. There is no reliable way to determine if a query was auto-parameterized in SSMS, it only indicates that auto-param was attempted. To be clear, the presence of place-holders like [@0] only shows that auto-parameterization was attempted. A reliable way to tell if a prepared plan was cached for reuse is to inspect the plan cache, where the 'parameterized plan handle' provides the link between ad-hoc and prepared plans.

    For example, the following query appears to be auto-parameterized in SSMS:

    SELECT *
    FROM dbo.ViewOnBaseTable
    WHERE ForeignKeyCol = 20;
    

    But the plan cache shows otherwise:

    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%';
    

    Adhoc plan cache entry

    If the database option for forced parameterization is enabled, we get a parameterized result, where the optimization is not applied:

    ALTER DATABASE Sandpit SET PARAMETERIZATION FORCED;
    DBCC FREEPROCCACHE;
    
    SELECT *
    FROM dbo.ViewOnBaseTable
    WHERE ForeignKeyCol = 20;
    

    Forced parameterization plan

    The plan cache query now shows a parameterized cached plan, linked by the parameterized plan handle:

    Parameterized plan cache

    Workaround

    Where possible, my preference is to rewrite the view as an in-line table-valued function, where the intended position of the selection can be made more explicit (if necessary):

    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;
    

    The query becomes:

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

    With the execution plan:

    Function plan

    0 讨论(0)
  • 2020-12-05 19:28

    I think in this particular case it may be because the data types between your parameters and your table do not match exactly so SQL Server has to do an implicit conversion which is not a sargable operation.

    Check your table data types and make your parameters the same type. Or do the cast yourself outside the query.

    0 讨论(0)
提交回复
热议问题