【问题标题】:SQL Query execution shortcut OR logic?SQL查询执行快捷方式或逻辑?
【发布时间】:2011-01-09 06:55:00
【问题描述】:

我有三张桌子:

SmallTable
   (id int, flag1 bit, flag2 bit)
JoinTable
   (SmallTableID int, BigTableID int)
BigTable
   (id int, text1 nvarchar(100), otherstuff...)

SmallTable 最多有几十条记录。 BigTable 有几百万,实际上是一个视图,将这个数据库中的一个表与同一服务器上另一个数据库中的一个表联合起来。

这是连接逻辑:

SELECT * FROM
    SmallTable s
    INNER JOIN JoinTable j ON j.SmallTableID = s.ID
    INNER JOIN BigTable b ON b.ID = j.BigTableID
WHERE
    (s.flag1=1 OR b.text1 NOT LIKE 'pattern1%')
    AND (s.flag2=1 OR b.text1 <> 'value1')

平均连接大小是几千个结果。显示的所有内容均已编入索引。

对于大多数 SmallTable 记录,flag1flag2 设置为 1,因此实际上甚至不需要访问 BigTable.text1 上的索引,但 SQL Server 无论如何都会这样做,从而导致代价高昂的 Indexed扫描和嵌套循环。

有没有更好的方法来提示 SQL Server,如果 flag1flag2 都设置为 1,它甚至不应该费心查看 text1

实际上,如果我可以在这些情况下完全避免连接到 BigTable(JoinTable 是托管的,所以这不会造成问题),这将使这个键查询更快。

【问题讨论】:

  • +1 有趣的问题。希望自己能从中学到更多!
  • 您提到了对BigTable 的索引扫描,这是一个视图。是索引视图,还是对基础表执行索引扫描?你能把查询计划贴在这里吗?

标签: sql-server performance tsql sql-execution-plan short-circuiting


【解决方案1】:

SQL 布尔求值确实保证运算符短路。请参阅On SQL Server boolean operator short-circuit 以获得一个清楚的示例,该示例显示了假设操作员短路如何导致正确性问题和运行时错误。

另一方面,我链接中的示例显示了对 SQL Server 的作用:提供 SQL 可以使用的访问路径。因此,与所有 SQL 性能问题和问题一样,真正的问题不在于 SQL 文本的表达方式,而在于您的存储设计。 IE。查询优化器可以使用哪些索引来满足您的查询?

【讨论】:

  • 我同意——这本身不是一个捷径问题,而是查询引擎发送工作以检查 text1 的值的问题,即使两个标志都设置为 1 . 这是一个索引操作,但不必要的 if SmallTable 中所有选定的记录都设置了这些标志。正如我在其他地方评论的那样,我认为查询优化器没有“足够智能”来避免 BigTable.text1 上的所有记录都不需要文本比较的工作分支是一个问题。
  • 不幸的是,没有过程/条件查询树运算符。换句话说,没有运营商说“如果条件为真,沿着这条路径走,否则沿着另一条路径走”。查询优化必须创建一个满足所有可能条件的计划,即使是那些概率非常低的条件。当存在 确定性 条件时,查询计划可以做很多技巧,例如。具有可信外部关系的连接中的两个表可能从计划中完全消除一个表。这就是所有 T-SQL 技巧发挥作用的地方,例如使用 UNION 而不是 OR。
  • 这是一个非常好的评论。我很难说出我的理解,但这个恕我直言是一个很好的解释。
【解决方案2】:

不幸的是,我不相信 SQL Server 会短路这种情况。

所以我建议做 2 个查询并将它们联合起来。第一个查询使用 s.flag1=1 和 s.flag2=1 WHERE 条件,第二个查询使用 s.flag11 和 s.flag21 条件连接到 BigTable。

This关于此事的文章值得一读,包括底线:

...SQL Server 不这样做 短路就像它在 其他编程语言和 你无法强迫它 到。

更新:
This 文章也是一篇有趣的文章,其中包含一些关于该主题的好链接,包括与 SQL Server 查询处理器团队的开发经理的技术网络聊天,其中简要介绍了提到优化器确实允许短路评估。我从各种文章中得到的总体印象是“是的,优化器可以发现短路的机会,但你不应该依赖它,也不能强迫它”。因此,我认为 UNION 方法可能是您最好的选择。如果它没有提出一个利用捷径机会的计划,那将取决于基于成本的优化器认为它找到了一个不这样做的合理计划(这将取决于索引、统计数据等) .

【讨论】:

  • 很好的评论,但是虽然 SQL Server 没有快捷表达式评估,但它确实进行查询优化。因此,如果我的查询仅限于 SmallTable 的结果集,其中两个标志都是 1,它应该足够聪明,可以跳过对 BigTable.text1 的检查,但这需要根据数据更改执行计划。我认为这是核心问题。
  • 是的,我明白你在说什么,但我仍然认为它是捷径——优化器需要能够看到短路的机会。过去我自己在这种事情上做过一些测试,这就是我所看到的(没有以这种方式进行优化)。
  • 问题是对于or,它可以按任何顺序进行评估,并且它首先在右侧进行。
  • @Hogan,我认为实际问题是 SQL Server 总是 both 并合并它们,没有意识到左侧可能总是返回“1”,因此使右侧检查成为多余。这是对查询使用单一、非过程执行计划的限制,而不是可能在运行时中断部分查询执行路径的过程检查。甚至 UNION 也执行 UNION 的所有方面,所以我认为它也无法在这里逃脱。带有 IF...ELSE...END 逻辑的存储过程可能是唯一的答案。
  • 请注意,这不是我第一次看到引用 Nigel Ellis 的聊天记录:聊天记录已于 2001 年 11 月 19 日发布到 TechNet。QO 发生了重大变化2005 年发布,2008 年也有更多变化。作为一般规则,来自 Craig 博客的信息明显是最新的 blogs.msdn.com/craigfr
【解决方案3】:

它并不优雅,但它应该可以工作......

SELECT * FROM
    SmallTable s
    INNER JOIN JoinTable j ON j.SmallTableID = s.ID
    INNER JOIN BigTable b ON b.ID = j.BigTableID
WHERE
    (s.flag1 = 1 and s.flag2 = 1) OR 
    (
       (s.flag1=1 OR b.text1 NOT LIKE 'pattern1%')
       AND (s.flag2=1 OR b.text1 <> 'value1')
    )

【讨论】:

  • 谢谢...试过了,但我知道的执行结果是一样的。
【解决方案4】:

SQL Server 通常会抓取子查询提示(尽管可以随意丢弃它):

SELECT      * 
FROM        (
            SELECT * FROM SmallTable where flag1 <> 1 or flag2 <> 1
            ) s
INNER JOIN  JoinTable j ON j.SmallTableID = s.ID
...

【讨论】:

    【解决方案5】:

    不知道如果没有测试数据这是否会更快......但听起来可能

    SELECT * FROM
        SmallTable s
        INNER JOIN JoinTable j ON j.SmallTableID = s.ID
        INNER JOIN BigTable b ON b.ID = j.BigTableID
    WHERE
        (s.flag1=1) AND (s.flag2=1)
     UNION ALL
     SELECT * FROM
        SmallTable s
        INNER JOIN JoinTable j ON j.SmallTableID = s.ID
        INNER JOIN BigTable b ON b.ID = j.BigTableID
    WHERE
        (s.flag1=0 AND b.text1 NOT LIKE 'pattern1%')
        AND (s.flag2=0 AND b.text1 <> 'value1')
    

    请告诉我发生了什么

    此外,您可以通过仅返回此查询的唯一 ID,然后使用该结果获取所有其余数据来加快此过程。

    编辑

    这样的?

    SELECT * FROM
        SmallTable s
        INNER JOIN JoinTable j ON j.SmallTableID = s.ID
        INNER JOIN BigTable b ON b.ID = j.BigTableID
    WHERE
        (s.flag1=1) AND (s.flag2=1)
     UNION ALL
     SELECT * FROM
        SmallTable s
        INNER JOIN JoinTable j ON j.SmallTableID = s.ID
        INNER JOIN BigTable b ON b.ID = j.BigTableID
    WHERE EXISTS
        (SELECT 1 from BigTable b
         WHERE   
        (s.flag1=0 AND b.text1 NOT LIKE 'pattern1%')
        AND (s.flag2=0 AND b.text1 <> 'value1')
    )
    

    【讨论】:

    • 试过这个,当任一标志设置为1时,它实际上要慢得多。
    • 我认为我们可以通过额外的连接和强制命令来做到这一点,但我的大脑现在想起来很模糊。
    【解决方案6】:

    希望这行得通 - 注意 case 语句中的快捷方式逻辑 aggregates 但是...

    SELECT * FROM
        SmallTable s
        INNER JOIN JoinTable j ON j.SmallTableID = s.ID
        INNER JOIN BigTable b ON b.ID = j.BigTableID
    WHERE 1=case when (s.flag1 = 1 and s.flag2 = 1) then 1
    when (
           (s.flag1=1 OR b.text1 NOT LIKE 'pattern1%')
           AND (s.flag2=1 OR b.text1 <> 'value1')
        ) then 1
    else 0 end

    【讨论】:

      猜你喜欢
      • 2010-10-21
      • 1970-01-01
      • 1970-01-01
      • 2012-07-18
      • 1970-01-01
      • 2018-09-30
      • 2020-02-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多