#### 8.2.2.3使用EXISTS策略优化子查询

[](<>)[](<>)

某些优化适用于使用`在里面`（或`=任何`)运算符来测试子查询结果。本节将讨论这些优化，特别是关于`无效的`价值观存在。讨论的最后一部分建议如何帮助优化器。

考虑下面的子查询比较：

```
outer_expr IN (SELECT inner_expr FROM ... WHERE subquery_where)
```

MySQL“从外部到内部”评估查询也就是说，它首先获得外部表达式的值*`外表`*，然后运行子查询并捕获它生成的行。

一个非常有用的优化是“通知”子查询，唯一感兴趣的行是那些内部表达式*`内表`*等于*`外表`*。这是通过在子查询的`哪里`条款，使其更具限制性。转换后的比较如下所示：

```
EXISTS (SELECT 1 FROM ... WHERE subquery_where AND outer_expr=inner_expr)
```

转换后，MySQL可以使用下推等式来限制它必须检查的行数，以评估子查询。

更一般地说，比较*`N`*返回的子查询的值*`N`*-值行进行相同的转换。如果*`奥伊`*和*`伊尤`*表示对应的外部和内部表达式值，此子查询比较：

```
(oe_1, ..., oe_N) IN
  (SELECT ie_1, ..., ie_N FROM ... WHERE subquery_where)
```

变成：

```
EXISTS (SELECT 1 FROM ... WHERE subquery_where
                          AND oe_1 = ie_1
                          AND ...
                          AND oe_N = ie_N)
```

为了简单起见，下面的讨论假设一对外部和内部表达式值。

如果满足以下任一条件，则上述“下推”策略有效：

-   *`外表`*和*`内表`*不可能`无效的`.

-   你不需要区分`无效的`从…起`错误的`子查询结果。如果子查询是[`或`](logical-operators.html#operator_or)或[`和`](logical-operators.html#operator_and)表达方式`哪里`子句中，MySQL假定您不在乎。优化器注意到的另一个实例`无效的`和`错误的`子查询结果不需要区分，因为此构造：

    ```
    ... WHERE outer_expr IN (subquery)
    ```

    在这种情况下`哪里`子句是否拒绝该行`在(*`子查询`*)`返回`无效的`或`错误的`.

    假设*`外表`*被认为是非-`无效的`值，但子查询不会生成这样的行：*`外表`* = *`内表`*然后`*`外表`*在（选择…）`评估如下：

-   `无效的`，如果[`选择`](select.html)产生任何一行*`内表`*是`无效的`

-   `错误的`，如果[`选择`](select.html)只生成非-`无效的`重视或不创造任何东西

    在这种情况下，查找具有`*`外表`* = *`内表`*`不再有效。有必要查找这样的行，但如果找不到，也要查找其中的行*`内表`*是`无效的`.大致来说，子查询可以转换为如下内容：


```
EXISTS (SELECT 1 FROM ... WHERE subquery_where AND
        (outer_expr=inner_expr OR inner_expr IS NULL))
```

评估额外成本的必要性[`是空的`](comparison-operators.html#operator_is-null)条件就是MySQL拥有[`ref_或_null`](explain-output.html#jointype_ref_or_null)访问方法：

```
mysql> EXPLAIN
       SELECT outer_expr IN (SELECT t2.maybe_null_key
                             FROM t2, t3 WHERE ...)
       FROM t1;
*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: t1
...
*************************** 2. row ***************************
           id: 2
  select_type: DEPENDENT SUBQUERY
        table: t2
         type: ref_or_null
possible_keys: maybe_null_key
          key: maybe_null_key
      key_len: 5
          ref: func
         rows: 2
        Extra: Using where; Using index
...
```

这个[`唯一子查询`](explain-output.html#jointype_unique_subquery)和[`索引子查询`](explain-output.html#jointype_index_subquery)子查询特定的访问方法也有“或”`无效的`“变种。

额外的`或是空的`条件使查询执行稍微复杂一些（子查询中的一些优化变得不适用），但通常这是可以接受的。

情况更糟的时候*`外表`*可以是`无效的`.根据SQL对`无效的`作为“未知值”`空输入（选择*`内表`* ...)`应评估：

-   `无效的`，如果[`选择`](select.html)生成任何行

-   `错误的`，如果[`选择`](select.html)不产生行

    为了进行正确的评估，必须能够检查[`选择`](select.html)已经产生了任何行，所以`*`外表`* = *`内表`*`无法下推到子查询中。这是一个问题，因为许多真实世界的子查询变得非常慢，除非可以向下推等式。

    本质上，必须有不同的方式来执行子查询，这取决于*`外表`*.

    优化器选择SQL遵从性而不是速度，因此它考虑了以下可能性：*`外表`*可能是`无效的`:

-   如果*`外表`*是`无效的`，要计算以下表达式，必须执行[`选择`](select.html)要确定它是否生成任何行，请执行以下操作：

    ```
    NULL IN (SELECT inner_expr FROM ... WHERE subquery_where)
    ```

    执行原件是必要的[`选择`](select.html)在这里，没有任何之前提到过的被压下的等式。

-   另一方面，当*`外表`*不是`无效的`，绝对有必要进行以下比较：

    ```
    outer_expr IN (SELECT inner_expr FROM ... WHERE subquery_where)
    ```

    转换为使用下推条件的表达式：

    ```
    EXISTS (SELECT 1 FROM ... WHERE subquery_where AND outer_expr=inner_expr)
    ```

    如果没有这种转换，子查询的速度会很慢。

    为了解决是否将条件下推到子查询中的难题，这些条件被包装在“触发器”函数中。因此，以下形式的表达式：


```
outer_expr IN (SELECT inner_expr FROM ... WHERE subquery_where)
```

转换为：

```
EXISTS (SELECT 1 FROM ... WHERE subquery_where
                          AND trigcond(outer_expr=inner_expr))
```

更一般地说，如果子查询比较基于几对外部和内部表达式，则转换将进行以下比较：

```
(oe_1, ..., oe_N) IN (SELECT ie_1, ..., ie_N FROM ... WHERE subquery_where)
```

并将其转换为以下表达式：

```
EXISTS (SELECT 1 FROM ... WHERE subquery_where
                          AND trigcond(oe_1=ie_1)
                          AND ...
                          AND trigcond(oe_N=ie_N)
       )
```

每个`三分之一秒(*`十、`*)`是一个特殊函数，其计算结果如下：

-   *`十、`*当“链接”外部表达式*`奥伊`*不是`无效的`

-   `符合事实的`当“链接”外部表达式*`奥伊`*是`无效的`

笔记

触发函数是*不*你创造的那种触发器[`创建触发器`](create-trigger.html).

包含在`trigcond（）`函数不是查询优化器的第一类谓词。大多数优化无法处理可能在查询执行时打开或关闭的谓词，因此它们假定`三分之一秒(*`十、`*)`成为一个未知函数并忽略它。触发式等式可用于以下优化：

-   参考优化：`三分之一秒(*`十、`*=*`Y`*[或*`Y`*是空的]）`可以用来构造[`裁判`](explain-output.html#jointype_ref), [`eq_ref`](explain-output.html#jointype_eq_ref)或[`ref_或_null`](explain-output.html#jointype_ref_or_null)表访问。

-   基于索引查找的子查询执行引擎：`三分之一秒(*`十、`*=*`Y`*)`可以用来构造[`唯一子查询`](explain-output.html#jointype_unique_subquery)或[`索引子查询`](explain-output.html#jointype_index_subquery)访问。

-   表条件生成器：如果子查询是多个表的联接，则会尽快检查触发的条件。

    当优化器使用触发的条件创建某种基于索引查找的访问时（对于前面列表的前两项），它必须有一个用于关闭条件的回退策略。这个后备策略总是一样的：进行一次完整的表扫描。在里面[`解释`](explain.html)输出时，后备方案显示为`对空密钥进行完全扫描`在`额外的`专栏：


```
mysql> EXPLAIN SELECT t1.col1,
       t1.col1 IN (SELECT t2.key1 FROM t2 WHERE t2.col2=t1.col2) FROM t1\G
*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: t1
        ...
*************************** 2. row ***************************
           id: 2
  select_type: DEPENDENT SUBQUERY
        table: t2
         type: index_subquery
possible_keys: key1
          key: key1
      key_len: 5
          ref: func
         rows: 2
        Extra: Using where; Full scan on NULL key
```

如果你跑[`解释`](explain.html)然后[`显示警告`](show-warnings.html)，您可以看到触发条件：

```
*************************** 1. row ***************************
  Level: Note
   Code: 1003
Message: select `test`.`t1`.`col1` AS `col1`,
         <in_optimizer>(`test`.`t1`.`col1`,
         <exists>(<index_lookup>(<cache>(`test`.`t1`.`col1`) in t2
         on key1 checking NULL
         where (`test`.`t2`.`col2` = `test`.`t1`.`col2`) having
         trigcond(<is_not_null_test>(`test`.`t2`.`key1`))))) AS
         `t1.col1 IN (select t2.key1 from t2 where t2.col2=t1.col2)`
         from `test`.`t1`
```

触发条件的使用对性能有一定影响。A.`空（选择…）`expression now可能会导致以前没有的全表扫描（速度很慢）。这是正确结果的代价（触发条件策略的目标是提高遵从性，而不是速度）。

对于多个表子查询，执行`空（选择…）`速度特别慢，因为连接优化器不会针对外部表达式不可用的情况进行优化`无效的`。它假定子查询求值`无效的`即使有统计数据表明情况并非如此，左边的情况也非常罕见。另一方面，如果外部表达式可能是`无效的`但事实上从来都不是这样，没有表现惩罚。

为了帮助查询优化器更好地执行查询，请使用以下建议：

-   将列声明为`非空`如果真是的话。通过简化列的条件测试，这也有助于优化器的其他方面。

-   如果你不需要区分`无效的`从…起`错误的`子查询结果，可以轻松避免执行路径缓慢。替换如下所示的比较：

    ```
    outer_expr [NOT] IN (SELECT inner_expr FROM ...)
    ```

    用这个表达：

    ```
    (outer_expr IS NOT NULL) AND (outer_expr [NOT] IN (SELECT inner_expr FROM ...))
    ```

    然后`空（选择…）`因为MySQL停止计算，所以永远不会计算[`和`](logical-operators.html#operator_and)一旦表达结果清晰，就立即分开。

    另一种可能的重写：

    ```
    [NOT] EXISTS (SELECT inner_expr FROM ...
            WHERE inner_expr=outer_expr)
    ```

    这个[`子查询_物化_基于成本`](switchable-optimizations.html#optflag_subquery-materialization-cost-based)美国国旗[`优化器_开关`](server-system-variables.html#sysvar_optimizer_switch)系统变量可以控制子查询具体化和`在里面`-到-`存在`子查询转换。看见[第8.9.2节，“可切换优化”](switchable-optimizations.html).
