我有一个用户定义的存储过程当调用而不传递显式值时,我只想传递所有位置 (nvarchar(50)),这是表的主键字段:Monitor_Locations(约 850 个条目)
SP 的一部分定义如下(截取)。
ALTER PROCEDURE [dbo].[dev_Tech@Locs2b] ( --CREATE or ALTER
@Locations as nvarchar(MAX) = NULL -- = 'GG1,BenBr14,BenBr00,YB_ToeDrain_Base'
,@rangeStart as DateTime = '1970-01-01'
,@rangeEnd as DateTime = '2099-12-31'
) AS BEGIN
SET NOCOUNT ON; --otherwise concrete5 chokes for multi-table returns.
DECLARE @loclist as TABLE (
Location nvarchar(50) PRIMARY KEY
)
IF @Locations is NULL
INSERT INTO @loclist(Location)
SELECT Location from Monitor_Locations order by Location
ELSE --irrelevant for this question
INSERT INTO @loclist(Location)
SELECT
ML.Location
FROM Monitor_Locations as ML join
tvf_splitstring(@Locations) as ss ON
ML.Location=ss.Item OR
ML.Location like ss.Item+'[_]%'
ORDER BY ML.Location;
With Deploys as (
SELECT
D.Location,
MIN(D.Start) as Start,
MAX(D.[Stop]) as Stop
FROM
Deployments as D
WHERE
D.Stop is not NULL
)
...做一些其他的事情...
为了在将受限站点列表发送到 SP 时提高存储过程的速度,我想将 WHERE 子句替换为
WHERE
CASE
WHEN D.Stop IS NULL THEN 0
WHEN @Locations IS NULL THEN 1 -- full list, so binding to another list doesn't do us any good.
WHEN EXISTS (SELECT 1 from (SELECT Location from @loclist as l where l.Location=D.Location) as ll) THEN 1 --ELSE NULL which is not 1
END=1
但是 SP 曾经需要 6-8 秒来执行,现在需要 2.5 分钟(对于没有限制列表的调用)。我认为完整列表的每种方式花费的时间大致相同,因为 CASE 的第二个子句应该很快被触发,而第三个子句永远不会被检查。
发生什么了?这段代码:
WHERE
CASE
WHEN D.Stop IS NULL THEN NULL
WHEN @Locations IS NULL THEN 1 -- full list, so binding to another list doesn't do us any good.
WHEN EXISTS (SELECT 1 from (SELECT Location from @loclist as l where l.Location=D.Location) as ll) THEN 1 --else null
END is not null
这个计划需要大约 10 分钟的运行时间:
对比这里的WHERE D.Stop is not NULL
计划(6s):
有一次,这个 SP 在这个版本中花费了 1 秒,但是通过更改 SP 然后再变回去,又花了 6 秒。如答案中所述,这可能是由于参数嗅探。
运行时间
我的目标执行时间少于 2 秒,因为这将是 Web 应用程序上频繁执行的 SP,它使用它来填充和限制其他用户选择。基本上,我不希望这是一个明显的瓶颈。初始运行时间大约为 3 分钟,但在添加或更改一些索引后,该时间下降到 6-8 秒范围。
星期一 (2016-08-29),在进行重大更改之前 没有输入参数的简单 WHERE:5 秒带有 rangeStart 和 rangeEnd 的简单 WHERE:4 秒带有 @Locations 设置为 7 元素 CSV 变量的简单 WHERE CASEd WHERE:最多 10 分钟
重新处理 CLR 函数后(见下面我的回答)星期二(2016 年 8 月 30 日)没有输入参数的简单或 CASEd WHERE 或带有 rangeStart 和 rangeEnd 的简单或 CASEd WHERE:3s 简单或 CASEd WHERE 有 7 个元素 @Locations:0 -1s
将表变量@loclist 迁移到临时表#loclist 后所有测试的 WHEREs/参数:0-1s
两大性能问题:
在您现在提供的执行计划中,该函数显示 0% 的成本,但事实并非如此。函数成本较高,但您不会在执行计划中看到实际成本,除非它是内联表值函数。
闻起来有参数嗅探的味道。
因为你分享了图片,所以我无法详细说明你的问题。我标记了两个执行计划之间的主要区别。
在第一个计划中,SQL Server 为查询创建了执行计划并使用了非聚集索引查找,但在第二个计划中它使用了索引扫描。这是增加总执行时间的主要原因。
索引查找:- 仅触及符合条件的行和包含这些符合条件的行的页面。
索引扫描:- 接触表/索引中的每一行,无论它是否合格。
当您在 where 条件下操作数据(列)(使用函数或 case 语句)时,forst SQL Server 扫描完整的索引/表,然后执行操作数据并匹配您的条件。此过程会增加内存利用率、磁盘 IO 并增加执行时间。
包括 Tara Kizer 的建议,我建议
或者
提取临时表中的所有记录,在其上创建索引并将数据插入其中,包括 CASE 语句并从临时表中检索数据。
谢谢
概括
在实施所提供答案中的一些建议后,无论使用的参数值如何,整个 SP 似乎都在 0-1 秒内执行。非常感谢所有提供帮助的人。
如果这似乎会在将来影响性能(或将此结果绑定到另一个表),我将研究 Rajesh 关于在临时表中存储“条件”值的建议。
尚未解决的问题
我不确定为什么它使用聚集索引扫描而不是寻找以下内容:
然而,这确实是一个寻求
此外,在周末,我正在研究全文索引是否对我的
LIKE x+'[_]%'
加入有益,但我无法弄清楚默认的分词器是什么('_'
语言 1033 是否将单词标记分开?或者只是真正的空白字符?)和我似乎没有安装全文索引(SELECT * from sys.fulltext_languages
返回一个空的结果集,就像那样EXEC sp_help_fulltext_system_components
)。由于我没有安装媒体,我需要等待 IT 重新安装 SQL Server 2008 R2 以添加全文功能,这甚至可能对我没有好处。但是,正如我所说,整个混乱需要 0-1 秒才能执行,所以我现在很满意。
整个存储过程
执行计划gif
最终方案
C# CLR 函数
因为我以前从未用 C# 编程过,所以我认为将我的代码修改记录在此处评论中链接的 CLR 中是个好主意。
clr_splitString_delim(适用于 .NET Framework 3.5)基于 Adam Machanic 的SQLCLR String Splitter:
在 VisStudio 中创建 DLL 后,将 CLR 组装到数据库中: