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

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

查询执行计划

4

3 回答 3

24

使用时OPTION (RECOMPILE)请务必查看执行后(“实际”)计划,而不是执行前(“估计”)计划。一些优化仅在执行时应用:

DECLARE @ForeignKeyCol int = 20;

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

执行前计划:

执行前计划

执行后计划:

执行后计划

在 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 中自动参数化,它仅表示尝试了自动参数。需要明确的是,占位符 like 的存在仅表明尝试了自动参数化。判断一个准备好的计划是否被缓存以供重用的可靠方法是检查计划缓存,其中“参数化计划句柄”提供了临时计划和准备好的计划之间的链接。[@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 回答