如果在WHERE子句中,常量被参数替换(具有相同的值),为什么查询会大幅减慢?

Zol*_*tan 11 sql where-clause sql-server-2008

我有一个递归查询,如果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
Run Code Online (Sandbox Code Playgroud)

查询#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
Run Code Online (Sandbox Code Playgroud)

对于具有50,000行的表,具有常量的查询运行10毫秒,具有该参数的查询运行30秒(慢3000倍).

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

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

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

如果有人想我可以发布表定义和一些示例数据.我正在使用SQL 2008 R2.

提前谢谢你的帮助!

执行计划 - 查询#1 - 使用常量

替代文字

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

替代文字

Zol*_*tan 5

正如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
)
Run Code Online (Sandbox Code Playgroud)

视图

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

用常量查询

SELECT * FROM TestView WHERE Id = 69
Run Code Online (Sandbox Code Playgroud)

使用参数查询

DECLARE @Id INT
SELECT @Id = 69
SELECT * FROM TestView WHERE Id = @Id
Run Code Online (Sandbox Code Playgroud)

使用parmater的查询基本上与使用常量的查询一样快.

感谢Martin和其他人!