【问题标题】:Why does a query slow down drastically if in the WHERE clause a constant is replaced by a parameter (having the same value)?如果在 WHERE 子句中将常量替换为参数(具有相同的值),为什么查询会急剧变慢?
【发布时间】:2010-11-19 14:40:19
【问题描述】:

如果WHERE 子句包含一个常量,我有一个递归查询,它执行得非常快,但如果我用具有相同值的参数替换该常量,它会变得非常慢。

查询 #1 - 使用常量

;WITH Hierarchy (Id, ParentId, Data, Depth)
AS
( SELECT Id, ParentId, NULL AS Data, 0 AS Depth
  FROM Test
  UNION ALL
  SELECT h.Id, t.ParentId, COALESCE(h.Data, t.Data), Depth + 1 AS Depth
  FROM Hierarchy h
       INNER JOIN Test t ON t.Id = h.ParentId
)
SELECT *
FROM Hierarchy
WHERE Id = 69

查询 #2 - 带参数

DECLARE @Id INT
SELECT @Id = 69

;WITH Hierarchy (Id, ParentId, Data, Depth)
AS
( SELECT Id, ParentId, NULL AS Data, 0 AS Depth
  FROM Test
  UNION ALL
  SELECT h.Id, t.ParentId, COALESCE(h.Data, t.Data), Depth + 1 AS Depth
  FROM Hierarchy h
       INNER JOIN Test t ON t.Id = h.ParentId
)
SELECT *
FROM Hierarchy
WHERE Id = @Id

如果表有 50,000 行,带有常量的查询运行 10 毫秒,带有参数的查询运行 30 秒(慢 3,000 倍)。

将最后一个WHERE 子句移动到递归的锚定义不是一个选项,因为我想使用查询来创建一个视图(没有最后一个WHERE)。视图中的选择将具有 WHERE 子句 (WHERE Id = @Id) - 由于 Entity Framework,我需要它,但这是另一回事。

任何人都可以建议一种方法来强制查询 #2(使用参数)使用与查询 #1(使用常量)相同的查询计划吗?

我已经尝试过使用索引,但没有帮助。

如果有人愿意,我也可以发布表定义和一些示例数据。 我正在使用 SQL 2008 R2。

提前感谢您的帮助!

执行计划 - 查询 #1 - 带有常量

执行计划 - 查询 #2 - 带参数

【问题讨论】:

  • 请检查新添加的执行计划。
  • 看起来好的计划将谓词推得更远了。类似于本文第二部分讨论的问题jahaines.blogspot.com/2010/03/…
  • 你说得对,我只是想“下推谓词”。创建一个用户定义的表值函数和 CROSS APPLY 解决了我的问题,谢谢!

标签: sql sql-server-2008 where-clause


【解决方案1】:

正如 Martin 在问题下的评论中建议的那样,问题在于 SQL 服务器没有正确地从 WHERE 子句中下推谓词 - 请参阅他评论中的链接。

我最终创建了一个用户定义的表值函数并将其与 CROSS APPLY 运算符一起用于创建视图。

让我们看看解决方案本身。

用户定义的表值函数

CREATE FUNCTION [dbo].[TestFunction] (@Id INT)
RETURNS TABLE 
AS
RETURN 
(
    WITH
    Hierarchy (Id,  ParentId, Data, Depth)
    AS(
    SELECT Id, ParentId, NULL AS Data, 0 AS Depth FROM Test Where Id = @Id
    UNION ALL
    SELECT h.Id, t.ParentId, COALESCE(h.Data, t.Data), Depth + 1 AS Depth
        FROM Hierarchy h
            INNER JOIN Test t ON t.Id = h.ParentId
    )
    SELECT * FROM Hierarchy
)

查看

CREATE VIEW [dbo].[TestView]
AS
SELECT t.Id, t.ParentId, f.Data, f.Depth
FROM
    Test AS t
    CROSS APPLY TestFunction(Id) as f

使用常量查询

SELECT * FROM TestView WHERE Id = 69

带参数查询

DECLARE @Id INT
SELECT @Id = 69
SELECT * FROM TestView WHERE Id = @Id

带有参数的查询的执行速度基本上与带有常量的查询一样快。

感谢马丁和其他人!

【讨论】:

    【解决方案2】:

    对于您的第二个查询,请尝试使用 OPTIMIZE FOR 或 OPTION(RECOMPILE) query hint 来查看是否会根据提供的参数值强制它重新编译。

    【讨论】:

    • 如果 SQL 会正确下推查询的 WHERE 子句,这将是一个解决方案,例如@Id = 69 查询运行得非常快。然而事实并非如此。
    【解决方案3】:

    您应该使用plan guide 来冻结您想要的计划。

    【讨论】:

    • 这个怎么生成?是否可以直接采用常量计划,然后以某种方式破解 XML 以合并参数?
    • 我最终得出了与 Martin 相同的结论,是否可以/容易地使用文字查询的计划指南并将其应用于带有参数的查询?
    【解决方案4】:

    这可能是有史以来最糟糕的建议,但是您是否考虑过创建一个存储过程来将您的查询创建为一个字符串并使用 sp_executesql 执行它?

    我对 sp_executesql 执行的 SQL 的缓存行为一无所知,这只是我脑海中浮现的第一件事。

    【讨论】:

    • 或者甚至只是 EXEC 命令。我很想知道它是否有帮助。
    • 问题是我想避免完全存储过程。以视图结尾是必需的,因为可以编写 SELECT FROM 视图,但对于存储过程而言并非如此。关于您的建议,即使我没有尝试过,但据我了解 SQL Server,如果遇到我的问题,它不会有任何区别。该参数仍将存在于查询中,因此执行计划将保持不变。
    • 你错过了我的(可能不清楚)点。您可以使用该参数来编写无参数查询。如果你的参数@Id 等于 100(比如说),你可以写一个 SQL 字符串 "SELECT..... WHERE @Id =' + @Id,然后执行这个 SQL 字符串。
    • 我可能遗漏了一些东西,但我认为您确实在查询视图并不重要。如您所说,创建不带参数的视图,并确保在查询视图时使用字符串连接以避免使用参数。
    • 如果您使用字符串连接来创建查询,您还需要考虑 SQL 注入。假设 @Id 是一个 int,您可以在进行连接之前检查它是一个 int。那应该可以解决您的问题。
    猜你喜欢
    • 1970-01-01
    • 2021-08-26
    • 1970-01-01
    • 1970-01-01
    • 2019-06-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-30
    相关资源
    最近更新 更多