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)
有了执行计划: