优化包含窗口函数的参数化T-SQL查询的执行计划

Joh*_*soe 20 t-sql sql-server query-optimization sql-server-2008-r2

编辑:我已经更新了示例代码并提供了完整的表和视图实现以供参考,但基本问题仍未改变.

我在数据库中有一个相当复杂的视图,我试图查询.当我尝试通过将WHERE子句硬编码为特定外键值从视图中检索一组行时,视图执行速度非常快,并且具有最佳执行计划(正确使用索引等)

SELECT * 
FROM dbo.ViewOnBaseTable
WHERE ForeignKeyCol = 20
Run Code Online (Sandbox Code Playgroud)

但是,当我尝试向查询添加参数时,我的执行计划突然崩溃了.当我运行下面的查询时,我正在获取索引扫描而不是遍布整个地方并且查询性能非常差.

DECLARE @ForeignKeyCol int = 20

SELECT * 
FROM dbo.ViewOnBaseTable
WHERE ForeignKeyCol = @ForeignKeyCol 
Run Code Online (Sandbox Code Playgroud)

我正在使用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
Run Code Online (Sandbox Code Playgroud)

我确定窗口函数是问题,但我通过窗口函数分区的单个值过滤我的查询,所以我希望优化器先过滤然后运行窗口函数.它在硬编码示例中执行此操作,但不是参数化示例.以下是两个查询计划.最佳计划是好的,底层计划是坏的.

查询执行计划

小智 24

使用时OPTION (RECOMPILE)一定要查看执行后("实际")计划而不是执行前('估计')计划.某些优化仅在执行时应用:

DECLARE @ForeignKeyCol int = 20;

SELECT ForeignKeyCol, ForeignKeyRank
FROM dbo.ViewOnBaseTable
WHERE ForeignKeyCol = @ForeignKeyCol
OPTION (RECOMPILE);
Run Code Online (Sandbox Code Playgroud)

执行前计划:

执行前计划

执行后计划:

执行后计划

在SQL Server 2012 build 11.0.3339和SQL Server 2008 R2 build 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;
Run Code Online (Sandbox Code Playgroud)

但计划缓存显示否则:

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%';
Run Code Online (Sandbox Code Playgroud)

即席计划缓存条目

如果启用了强制参数化的数据库选项,则会获得参数化结果,其中未应用优化:

ALTER DATABASE Sandpit SET PARAMETERIZATION FORCED;
DBCC FREEPROCCACHE;

SELECT *
FROM dbo.ViewOnBaseTable
WHERE ForeignKeyCol = 20;
Run Code Online (Sandbox Code Playgroud)

强制参数化计划

计划缓存查询现在显示参数化缓存计划,该计划由参数化计划句柄链接:

参数化计划缓存

解决方法

在可能的情况下,我倾向于将视图重写为内联表值函数,其中选择的预期位置可以更明确(如果需要):

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;
Run Code Online (Sandbox Code Playgroud)

查询变为:

DECLARE @ForeignKeyCol integer = 20;
SELECT pvobt.*
FROM dbo.ParameterizedViewOnBaseTable(@ForeignKeyCol) AS pvobt;
Run Code Online (Sandbox Code Playgroud)

有了执行计划:

功能计划