逻辑运算符使用不当导致查询性能不佳

Pan*_*tea 8 sql-server query t-sql sql-server-2016 query-performance

我有一个包含大量数据(近 1500 万)且结构如下的表。

create table test
(a int,--> /* There is a normal index on this column */
 b int,
<other columns>)
Run Code Online (Sandbox Code Playgroud)

有一个从此表中进行选择的查询,其中 where 子句中的条件之一是:

where a!=1 or (a=1 and b!=0) /* The original condition */
Run Code Online (Sandbox Code Playgroud)

查询非常慢,我认为这种糟糕的性能大部分可能是由于逻辑运算符使用不当造成的。我已经改变了条件,如下所示:

where not (a=1 and b=0) /* The edited version*/
Run Code Online (Sandbox Code Playgroud)

并且性能发生了巨大的变化!我需要确定的是这两个条件完全相同,这样我就不会错过任何数据。我想知道您是否可以帮助我解决这个问题并告诉我您是否有更好的选择来治疗这种情况。

如果您知道任何有关逻辑运算的正确使用以及/顺序优化器处理它们的方式的文章,请分享链接。

提前致谢

And*_*y M 24

为了查明两个条件是否真正相等,您可以尝试为每个条件构建真值表并查看这两个表是否相同。

以下是构建真值表的方法。您有两个变量,a,它可能等于也可能不等于 1,以及b,它可能等于也可能不等于 0。编写并执行如下查询:

SELECT
  a
, b
, [a!=1 or (a=1 and b!=0)] = CASE WHEN a!=1 or (a=1 and b!=0) THEN 'True' ELSE 'False' END
, [not (a=1 and b=0)]      = CASE WHEN not (a=1 and b=0)      THEN 'True' ELSE 'False' END
FROM
  (
    VALUES
      (   1,    0)
    , (   1, 9999)
    , (9999,    0)
    , (9999, 9999)
  ) AS v (a, b)
;
Run Code Online (Sandbox Code Playgroud)

对于每个变量,指定与该变量进行比较的值,以便进行相应的比较 true 或 false(取决于它是=!=),以及产生相反结果的另一个值。上面的值 9999 只是一个任意值,在与 比较时代表“非 1” a,在与 比较时代表“非 0” b。(我选择了与 1 或 0 完全不同的东西,以免使结果表太混乱。)

上述查询将返回以下输出:

A a!=1 或 (a=1 且 b!=0) 不是(a=1 且 b=0)
1 0 错误的 错误的
1 9999 真的 真的
9999 0 真的 真的
9999 9999 真的 真的

正如您所看到的,对于相同的输入值,两个表达式给出相同的结果。

但请注意,上表仅包含使比较计算结果为TrueFalse的值。这就是布尔代数中通常的情况。然而,在 SQL 世界中,布尔表达式可以计算出第三种状态,即Unknown又名Null。如果a可以为 null 并且确实为 null,则a=1(或a!=1就此而言)将计算为Unknown / Null。如果需要考虑可空性,那么我们的真值表应包含空值作为输入值。

以下是上述脚本的修改版本,其中两个变量都为空:

SELECT
  a
, b
, [a!=1 or (a=1 and b!=0)] = CASE
                               WHEN      a!=1 or (a=1 and b!=0)  THEN 'True'
                               WHEN NOT (a!=1 or (a=1 and b!=0)) THEN 'False'
                               ELSE 'Unknown'
                             END
, [not (a=1 and b=0)]      = CASE
                               WHEN      not (a=1 and b=0)       THEN 'True'
                               WHEN NOT (not (a=1 and b=0)     ) THEN 'False'
                               ELSE 'Unknown'
                             END
FROM
  (
    VALUES
      (   1,    0)
    , (   1, 9999)
    , (   1, NULL)
    , (9999,    0)
    , (9999, 9999)
    , (9999, NULL)
    , (NULL,    0)
    , (NULL, 9999)
    , (NULL, NULL)
  ) AS v (a, b)
;
Run Code Online (Sandbox Code Playgroud)

它给出以下输出:

A a!=1 或 (a=1 且 b!=0) 不是(a=1 且 b=0)
1 0 错误的 错误的
1 9999 真的 真的
1 无效的 未知 未知
9999 0 真的 真的
9999 9999 真的 真的
9999 无效的 真的 真的
无效的 0 未知 未知
无效的 9999 未知 真的
无效的 无效的 未知 未知

上面突出显示的是两个条件不会产生相同结果的情况,即当a为 null 并且b是非 0 的非空值时。在这种情况下,第一个条件的结果未知,而另一个条件的结果为 true。

同样,这是假设a可以为 null,并且在该假设下,您的两个逻辑表达式不相等。但是,例如,如果 only bcan be null 且a不能为 null,那么您可以从上面的输出中看到相应行中的结果是相同的。

因此,您将根据所涉及变量的可空性找到答案。

更多阅读的几个链接:

  • @Pantea 对于 `a!=1`,如果 a 为 Null,那么这将变为 `Null!=1` is Null。同样,对于“(a=1 and b!=0)”,如果 a 为 Null,则变为“(Null=1 and b!=0)”,从而简化为“(Null and b!=0)”,这只能是假或空。结合起来我们得到“Null or (Null and b!=0)”。`Null or True` 是 True,但 `Null or {False, Null}` 是 Null。因为我们已经知道 `(Null and b!=0)` 只能是 False 或 Null,所以我们有 `Null or {False, Null}`。在这两种情况下,结果都将为 Null。 (2认同)
  • (我使用的基本规则是“Null and {T,F,N} ==&gt; {N,F,N}”和“Null or {T,F,N} ==&gt; {T,N,N}” `) (2认同)