【问题标题】:SQL Server Query: Fast with Literal but Slow with VariableSQL Server 查询:文字速度快,变量速度慢
【发布时间】:2010-12-16 09:55:35
【问题描述】:

我有一个使用 CTE 从表中返回 2 个整数的视图。如果我像这样查询视图,它会在不到一秒的时间内运行

SELECT * FROM view1 WHERE ID = 1

但是,如果我像这样查询视图,则需要 4 秒。

DECLARE @id INT = 1
SELECT * FROM View1 WHERE ID = @id

我检查了 2 个查询计划,第一个查询正在对返回 1 条记录的主表执行聚集索引查找,然后将视图查询的其余部分应用于该结果集,而第二个查询正在执行索引扫描返回大约 3000 条记录,而不仅仅是我感兴趣的记录,然后过滤结果集。

在尝试让第二个查询使用索引搜索而不是索引扫描时,我是否缺少任何明显的东西。我正在使用 SQL 2008,但我所做的任何事情也需要在 SQL 2005 上运行。起初我以为这是某种参数嗅探问题,但即使我清除缓存,我也会得到相同的结果。

【问题讨论】:

    标签: sql-server performance sql-server-2008


    【解决方案1】:

    可能是因为在参数情况下,优化器无法知道该值不为空,所以它需要创建一个计划,即使它是也返回正确的结果。如果您有 SQL Server 2008 SP1,您可以尝试将 OPTION(RECOMPILE) 添加到查询中。

    【讨论】:

    • +1 在这里像冠军一样工作(比尝试强制各种连接/计划等手动操作要少得多)
    • @pst - 这是正确的建议解决方案,但推理错误。 OP 有一个变量而不是参数。问题是选择性估计,因为 SQL Server 不进行变量嗅探。不是“制定处理NULL 的计划”
    • @MartinSmith 所有让我无法理解的小细节 :( 我所知道的是 SQL Server 很高兴地将 2 秒(在 SMSS 查询中)变成了我要停止它之后-10 分钟查询 SqlCommand (带占位符) 作为 Typed DataSet 的一部分。在尝试 OPTION(RECOMPILE) 路由之前,我更新了相关表上的所有统计信息(没有任何选项),但这似乎并没有解决问题. “创建一个处理NULL的计划”是什么意思?
    • @MartinSmith 啊。我刚刚跳到ATTENTION(GRABBING) 建议——在我的情况下,+1 是针对“我的问题已修复”:-)
    • 这里关于优化器允许空值可能性的建议帮助了我;我用COALESCE 语句包装了我的变量,SP 执行速度加快了。我应该提到我正在生成动态 SQL,并且仅在变量不为 null 时才添加 where 子句的该元素。
    【解决方案2】:

    您可以在查询中添加 OPTIMIZE FOR hint,例如

    DECLARE @id INT = 1
    SELECT * FROM View1 WHERE ID = @id OPTION (OPTIMIZE FOR (@ID = 1))
    

    【讨论】:

      【解决方案3】:

      在我的例子中,在 DB 表中,列类型被定义为 VarChar,而在参数化查询中,参数类型被定义为 NVarChar,这在实际执行计划中引入了CONVERT_IMPLICIT,以在比较之前匹配数据类型,这是造成母猪性能的罪魁祸首, 2 秒对 11 秒。只需更正参数类型,就可以使参数化查询与非参数化版本一样快。

      一种可能的方法是 CAST 参数,如下所示:

      SELECT ...
      FROM ...
      WHERE name = CAST(:name AS varchar)
      

      希望这可以帮助有类似问题的人。

      【讨论】:

        【解决方案4】:

        我自己遇到了这个问题,直接赋值 (WHERE UtilAcctId=12345) 的视图运行时间 WHERE UtilAcctId = @UtilAcctId)。
        后者的执行计划与我在整个表上运行视图没有什么不同。

        我的解决方案不需要大量的索引、优化器提示或长时间的统计更新。

        相反,我将视图转换为 User-Table-Function,其中参数是 WHERE 子句所需的值。事实上,这个 WHERE 子句嵌套了 3 个查询深度,它仍然有效,并且恢复到

        最终我将参数更改为 TYPE,即 UtilAcctIds (int) 表。然后我可以将 WHERE 子句限制为表中的一个列表。 WHERE UtilAcctId = [参数列表].UtilAcctId。 这效果更好。我认为 user-table-functions 是预编译的。

        【讨论】:

        • 哈,我在 8 年前写了这个问题,然后我又找到了它。在几个 SQL Server 版本之后,我又遇到了同样的问题。当使用参数时,SQL Server 没有正确地将过滤器提升到我的视图中的嵌套表查询中 - 手动完成此提升的 SPROC(或 TVF)会产生预期的快速性能。
        【解决方案5】:

        当 SQL 开始优化带有变量的查询的查询计划时,它将根据列匹配可用索引。在这种情况下,有一个索引,因此 SQL 认为它只会扫描索引以查找值。当 SQL 使用列和文字值制定查询计划时,它可以查看统计信息和值来决定是否应该扫描索引或查找是否正确。

        使用优化提示和一个值告诉 SQL “这是大多数时间会使用的值,所以要优化这个值”,并且存储一个计划,就像使用这个字面值一样。使用优化提示和 UNKNOWN 的子提示告诉 SQL 您不知道该值将是什么,因此 SQL 会查看列的统计信息并决定查找或扫描什么是最好的,并相应地制定计划。

        【讨论】:

          【解决方案6】:

          我自己也遇到过同样的问题,结果发现是缺少索引,涉及子查询结果的(左)连接。

          select *
          from foo A
          left outer join (
            select x, count(*)
            from bar
            group by x
          ) B on A.x = B.x
          

          为 bar.x 添加了一个名为 bar_x 的索引

          【讨论】:

            【解决方案7】:

            我知道这个问题早已得到解答,但我遇到了同样的问题,并且有一个相当简单的解决方案,不需要提示、统计更新、附加索引、强制计划等。

            基于上面的评论“优化器无法知道该值不为空”,我决定将值从变量移动到表中:

            原代码:

            declare @StartTime datetime2(0) = '10/23/2020 00:00:00'
            declare @EndTime datetime2(0) = '10/23/2020 01:00:00'
                
            SELECT * FROM ...
            WHERE 
            C.CreateDtTm >= @StartTime
            AND  C.CreateDtTm < @EndTime
            

            新代码:

            declare @StartTime datetime2(0) = '10/23/2020 00:00:00'
            declare @EndTime datetime2(0) = '10/23/2020 01:00:00'
            
            CREATE TABLE #Times (StartTime datetime2(0) NOT NULL, EndTime datetime2(0) NOT NULL)
            INSERT INTO #Times(StartTime, EndTime) VALUES(@StartTime, @EndTime)
            
            SELECT * FROM ...
            WHERE 
            C.CreateDtTm >= (SELECT MAX(StartTime) FROM #Times)
            AND  C.CreateDtTm < (SELECT MAX(EndTime) FROM #Times)
            

            这会立即执行,而不是原始代码需要几分钟(显然您的结果可能会有所不同)。

            我假设如果我将主表中的数据类型更改为 NOT NULL,它也可以正常工作,但由于系统限制,我目前无法对此进行测试。

            【讨论】:

              【解决方案8】:
              DECLARE @id INT = 1
              
              SELECT * FROM View1 WHERE ID = @id
              

              这样做

              DECLARE @sql varchar(max)
              
              SET @sql = 'SELECT * FROM View1 WHERE ID =' + CAST(@id as varchar)
              
              EXEC (@sql)
              

              解决你的问题

              【讨论】:

              • 请详细说明您的 sn-p 做了什么以及为什么它可以解决问题。
              • 请补充说明
              猜你喜欢
              • 2022-01-19
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-06-25
              • 2014-08-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多