1

我遇到了一个有趣的障碍(至少对我来说很有趣)。下面是我的查询的大致概念。假设 @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 吗?

4

3 回答 3

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 分支相互矛盾,所以它最终扫描整个表并一一检查行。

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

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

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

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

于 2009-11-10T17:27:23.360 回答
4

这与优化器处理“OR”类型逻辑的难度以及与参数嗅探有关的问题有关尝试将上面的查询更改为这里的帖子中提到的 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
...
于 2009-11-10T16:54:49.720 回答
0

我应该抵制减少重复的冲动……但是伙计,这对我来说真的不合适。

这种“感觉”会更好吗?

SELECT ...很多列和复杂的东西...
从
(
    选择我的PK
    来自 TBooks
    在哪里
    (--...一些条件)
    AND @AuthorType = 1 AND --...不同的条件)
    联合所有
    选择我的PK
    来自 TBooks
    在哪里
    (--...一些条件)
    AND @AuthorType = 2 AND --...不同的条件)
    联合所有
    ...
) 作为 B1
加入 TBooks 作为 B2
    在 B2.MyPK = B1.MyPK
JOIN ...其他表...

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

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

我们对非常大的表执行此操作,用户在查询内容上有很多选择。

声明 @MyTempTable 表
(
    MyPK int 非空,
    首要的关键
    (
        我的PK
    )
)

如果@LastName 不为空
开始
   插入@MyTempTable
   (
        我的PK
   )
   选择我的PK
   FROM MyNamesTable
   WHERE LastName = @LastName -- 假设我们有一个有效的索引
结尾
别的
如果@Country 不为空
开始
   插入@MyTempTable
   (
        我的PK
   )
   选择我的PK
   FROM MyNamesTable
   WHERE Country = @Country -- 也有关于这个的索引
结尾

... ETC

SELECT ... 展示栏
来自@MyTempTable AS T
    JOIN MyNamesTable AS N
        ON N.MyPK = T.MyPK -- 一个 PK 连接,V. 高效
    JOIN ...其他表...
        在 ....
WHERE(@LastName 为 NULL 或姓氏 @LastName)
      AND(@Country 为空或国家/地区@Country)

请注意,所有测试都是重复的 [从技术上讲,您不需要 @Lastname 一个 :)],包括(可以说)不在原始过滤器中创建 @MyTempTable 的模糊测试。

@MyTempTable 的创建旨在充分利用可用的任何参数。也许如果@LastName 和@Country 都可用,那么填充表格的效率要比它们中的任何一个都高得多,所以我们为这种情况创建了一个案例。

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

于 2009-11-10T17:53:46.177 回答