sp_executesql 和 VARCHAR 参数的性能问题

rno*_*nko 7 performance sql-server dynamic-sql sql-server-2008-r2 query-performance

Segments具有按 DEPARTMENT (VARCHAR(10)) 和 BDPID(VARCHAR(10)) 的索引。

第一个查询的执行时间为 34 秒

SELECT TOP 10 c.BDPID, seg.FINAL_SEGMENT
FROM Customers c
LEFT JOIN Segments seg
     ON   seg.DEPARTMENT  = 'DEP345'
     AND  seg.BDPID       = c.BDPID
Run Code Online (Sandbox Code Playgroud)

当我将 DEPARTMENT 的参数移动到变量时,执行时间变为 1 秒。执行计划#2(快速)

DECLARE @dd VARCHAR(10)
SET @dd = 'DEP345'
SELECT TOP 10 c.BDPID, seg.FINAL_SEGMENT
FROM Customers c
LEFT JOIN Segments seg
     ON   seg.DEPARTMENT  = @dd
     AND  seg.BDPID       = c.BDPID
Run Code Online (Sandbox Code Playgroud)

但我必须使用动态sql。当我将查询移动到 sp_execitesql 时,执行时间再次变为 34 秒。执行计划#3(慢)

EXECUTE sp_executesql
     'SELECT TOP 10 c.BDPID, seg.FINAL_SEGMENT
     FROM Customers c
     LEFT JOIN Segments seg
          ON   seg.DEPARTMENT  = @dd
          AND  seg.BDPID     = c.BDPID',
     '@dd VARCHAR(10)',
     @dd = 'DEP345'
Run Code Online (Sandbox Code Playgroud)

如何使用动态 sql 获得第二个查询的性能?

Eri*_*ing 7

你遇到的是局部变量的诅咒。

简而言之,当您声明一个变量然后在查询中使用它时,SQL 无法嗅探该值。

它有时会根据变量的使用方式使用幻数(对于BETWEEN, >, >= , <, <=, <>.

对于等式搜索,使用密度向量(尽管此处将列定义为唯一事项)但底线是基数估计通常很远。这里有更多信息。

这是您的快速查询计划:

坚果

对于参数化的动态 SQL,可以嗅探该值,最终会得到一个完全不同的计划和完全不同的估计。

坚果

如果你想要一个快速而肮脏的选择,你可以使用 OPTIMIZE FOR UNKNOWN

EXECUTE sp_executesql
    @stmt =  N'SELECT TOP 10 c.BDPID, seg.FINAL_SEGMENT
     FROM Customers c
     LEFT JOIN Segments seg
          ON   seg.DEPARTMENT  = @dd
          AND  seg.BDPID     = c.BDPID
          OPTION (OPTIMIZE FOR (@dd UNKNOWN));', 
          @params N'@dd VARCHAR(10)', @dd = 'DEP345'
Run Code Online (Sandbox Code Playgroud)

这将为您提供密度向量估计,但这是一个笨拙的解决方案,我有点讨厌它。当您TOP 10从查询中删除 时,这当然会消失,您在评论中提到的内容仅适用于这篇文章。

我更希望看到你做的是一些索引调整。我敢打赌该Segment表正在为聚集索引而哭泣——HEAP 中的许多行通常是一个麻烦的迹象(转发的提取会毁了你的一天)。

这里使用的非聚集索引没有覆盖,这也严重拖累了这个查询。

您可以RID Lookup通过添加FINAL_SEGMENT为包含列来摆脱。

坚果