通过添加未使用的WHERE条件来查询运行时间

时间:2009-11-10 16:40:24

标签: sql sql-server where-clause

我遇到了一个有趣的障碍(至少对我有趣)。下面是我的查询的一般概念。假设@AuthorType是存储过程的输入,并且每个地方都有不同的专门条件。

SELECT *
FROM TBooks
WHERE
(--...SOME CONDITIONS)
OR
(@AuthorType = 1 AND --...DIFFERENT CONDITIONS)
OR
(@AuthorType = 2 AND --...STILL MORE CONDITIONS)

对我来说有趣的是,如果我使用@AuthorType = 0执行此SP,它会比我删除最后两组条件(为@AuthorType的特殊值添加条件的条件)运行得慢。

SQL Server不应该在运行时意识到永远不会满足这些条件并完全忽略它们吗?我所经历的差异并不小;它大约是查询长度的两倍(1-2秒到3-5秒)。

我是否希望SQL Server能够为我进行过多的优化?我是否真的需要为特殊条件设置3个独立的SP?

3 个答案:

答案 0 :(得分:6)

  

SQL Server不应该实现   运行时那些条件会   永远不会被完全忽视它们?

不,绝对没有。这里有两个因素在起作用。

  1. SQL Server 保证布尔运算符短路。有关示例清楚地显示查询优化如何反转布尔表达式求值的顺序,请参阅On SQL Server boolean operator short-circuit。虽然初看起来这似乎是命令式C语言集的一个错误,但对于声明性的面向SQL的世界来说,这是正确的做法。

  2. OR是SQL SARGability的敌人。将SQL语句编译为执行计划,然后执行计划。该计划在调用之间重用(缓存)。因此,SQL编译器必须生成一个适合所有单独OR情况的单个计划(@ AuthorType = 1 AND @ AuthorType = 2 AND @ AuthorType = 3)。在生成查询计划时,就像@AuthorType在某种意义上一次拥有所有值一样。结果几乎总是最糟糕的计划,一个不能使任何索引受益,因为各个OR分支相互矛盾,所以最终扫描整个表并逐个检查行。

  3. 在你的情况下做的最好的事情,以及涉及布尔OR的任何其他情况,是将@AuthorType移到查询之外:

    IF (@AuthorType = 1)
      SELECT ... FROM ... WHERE ...
    ELSE IF (@AuthorType = 2)
      SELECT ... FROM ... WHERE ...
    ELSE ...
    

    因为每个分支都清楚地分成了自己的语句,所以SQL可以为每个单独的案例创建正确的访问路径。

    接下来最好的事情是使用UNION ALL,这是chadhoc已经建议的方式,并且是在视图或需要单个语句的其他地方(不允许IF)的正确方法。

答案 1 :(得分:4)

优化器处理“OR”类型逻辑与issues处理parameter sniffing的难度有多大。尝试将上面的查询更改为帖子here中提到的UNION方法。也就是说,你最终将多个语句联合在一起只有一个@AuthorType = x AND,允许优化器排除AND逻辑与给定的@AuthorType不匹配的部分,并依次搜索到适当的索引。看起来像这样:

SELECT *
FROM TBooks
WHERE
(--...SOME CONDITIONS)
AND @AuthorType = 1 AND --...DIFFERENT CONDITIONS)
union all
SELECT *
FROM TBooks
WHERE
(--...SOME CONDITIONS)
AND @AuthorType = 2 AND --...DIFFERENT CONDITIONS)
union all
...

答案 2 :(得分:0)

  

我应该反对减少重复的冲动......但男人,这对我来说真的不对。

这会“感觉”更好吗?

SELECT ... lots of columns and complicated stuff ...
FROM 
(
    SELECT MyPK
    FROM TBooks
    WHERE 
    (--...SOME CONDITIONS) 
    AND @AuthorType = 1 AND --...DIFFERENT CONDITIONS) 
    union all 
    SELECT MyPK
    FROM TBooks
    WHERE 
    (--...SOME CONDITIONS) 
    AND @AuthorType = 2 AND --...DIFFERENT CONDITIONS) 
    union all 
    ... 
) AS B1
JOIN TBooks AS B2
    ON B2.MyPK = B1.MyPK
JOIN ... other tables ...

伪表B1只是获取PK的WHERE子句。然后将其连接回原始表(以及所需的任何其他表)以获得“演示”。这样可以避免在每个UNION ALL

中复制Presentation列

您可以更进一步,首先将PK插入临时表,然后将其连接到表示方面的其他表。

我们为非常大的表执行此操作,其中用户可以在查询内容上进行大量选择。

DECLARE @MyTempTable TABLE
(
    MyPK int NOT NULL,
    PRIMARY KEY
    (
        MyPK
    )
)

IF @LastName IS NOT NULL
BEGIN
   INSERT INTO @MyTempTable
   (
        MyPK
   )
   SELECT MyPK
   FROM MyNamesTable
   WHERE LastName = @LastName -- Lets say we have an efficient index for this
END
ELSE
IF @Country IS NOT NULL
BEGIN
   INSERT INTO @MyTempTable
   (
        MyPK
   )
   SELECT MyPK
   FROM MyNamesTable
   WHERE Country = @Country -- Got an index on this one too
END

... etc

SELECT ... presentation columns
FROM @MyTempTable AS T
    JOIN MyNamesTable AS N
        ON N.MyPK = T.MyPK -- a PK join, V. efficient
    JOIN ... other tables ...
        ON ....
WHERE     (@LastName IS NULL OR Lastname @LastName)
      AND (@Country IS NULL OR Country @Country)

请注意,所有测试都会重复[技术上你不需要@Lastname one :)],包括那些(假设)不在原始过滤器中创建@MyTempTable的模糊文本。

@MyTempTable的创建旨在充分利用任何可用的参数。也许如果@LastName和@Country都可用,填充表的效率要高于其中任何一个,那么我们就为该场景创建一个案例。

缩放问题?查看正在进行的实际查询,并为可以改进的查询添加案例。