#### 8.2.2.1 使用 Semijoin 优化 IN 和 EXISTS 子查询谓词

转型

[](<>)

半连接是一种准备时转换，它支持多种执行策略，例如表拉出、重复清除、第一次匹配、松散扫描和物化。优化器使用半连接策略来改进子查询的执行，如本节所述。

对于两个表之间的内连接，连接从一个表返回一行的次数与另一个表中的匹配次数相同。但是对于某些问题，唯一重要的信息是是否有匹配，而不是匹配的数量。假设有表名为`班级`和`名册`分别列出课程课程和班级名册（每个班级注册的学生）中的班级。要列出实际有学生注册的课程，您可以使用此联接：

```
SELECT class.class_num, class.class_name
    FROM class
    INNER JOIN roster
    WHERE class.class_num = roster.class_num;
```

但是，结果会为每个注册学生列出每个班级一次。对于提出的问题，这是不必要的信息重复。

假如说`class_num`是主键`班级`表，可以通过使用重复抑制[`选择不同的`](select.html)，但是首先生成所有匹配的行只是为了稍后消除重复是低效的。

使用子查询可以获得相同的无重复结果：

```
SELECT class_num, class_name
    FROM class
    WHERE class_num IN
        (SELECT class_num FROM roster);
```

在这里，优化器可以识别出`在`子句要求子查询从`名册`桌子。在这种情况下，查询可以使用半连接；也就是只返回每行的一个实例的操作`班级`与中的行匹配`名册`.

以下语句，其中包含`存在`子查询谓词，等价于包含一个`在`子查询谓词：

```
SELECT class_num, class_name
    FROM class
    WHERE EXISTS
        (SELECT * FROM roster WHERE class.class_num = roster.class_num);
```

在 MySQL 8.0.16 及更高版本中，任何带有`存在`子查询谓词受制于与具有等价的语句相同的半连接变换`在`子查询谓词。

从 MySQL 8.0.17 开始，以下子查询被转换为反连接：

-   `不在（选择...从...）`

-   `不存在（选择...从...）`.

-   `IN (SELECT ... FROM ...) 不正确`

-   `EXISTS (SELECT ... FROM ...) 不正确`.

-   `IN (SELECT ... FROM ...) 为假`

-   `EXISTS (SELECT ... FROM ...) 是错误的`.

    简而言之，任何形式的子查询的否定`输入（选择...从...）`要么`存在（选择...从...）`被转换为反连接。

    反连接是一种只返回不匹配的行的操作。考虑此处显示的查询：


```
SELECT class_num, class_name
    FROM class
    WHERE class_num NOT IN
        (SELECT class_num FROM roster);
```

此查询在内部被重写为反连接`SELECT class_num, class_name FROM class ANTIJOIN roster ON class_num`，它返回中每一行的一个实例`班级`那是*不是*与中的任何行匹配`名册`.这意味着，对于每一行`班级`, 一旦找到匹配项`名册`, 中的行`班级`可以丢弃。

如果要比较的表达式可以为空，则在大多数情况下无法应用反连接转换。该规则的一个例外是`(... NOT IN (SELECT ...)) 不假`及其等价物`(... IN (SELECT ...)) 不正确`可以转化为反连接。

外部查询规范中允许外部联接和内部联接语法，表引用可以是基表、派生表、视图引用或公用表表达式。

在 MySQL 中，子查询必须满足这些条件才能作为半连接处理（或者，在 MySQL 8.0.17 及更高版本中，如果`不是`修改子查询）：

-   它必须是一个`在`,`= 任何`， 要么`存在`出现在顶层的谓词`在哪里`要么`在`子句，可能作为一个术语`和`表达。例如：

    ```
    SELECT ...
        FROM ot1, ...
        WHERE (oe1, ...) IN
            (SELECT ie1, ... FROM it1, ... WHERE ...);
    ```

    这里，`ot_*`一世`*`和`它_*`一世`*`表示查询的外部和内部部分中的表，以及`oe_*`一世`*`和`ie_*`一世`*`表示引用外部表和内部表中的列的表达式。

    在 MySQL 8.0.17 及更高版本中，子查询也可以是由`不是`,`是 [不是] 真的`， 要么`是 [不是] 错误的`.

-   应该是单人[`选择`](select.html)没有[`联盟`](union.html)结构体。

-   它不得包含`拥有`条款。

-   它不能包含任何聚合函数（无论是显式还是隐式分组）。

-   它不能有`限制`条款。

-   该语句不得使用`STRAIGHT_JOIN`外部查询中的连接类型。

    [](<>)

-   这`STRAIGHT_JOIN`修饰符不得存在。

    [](<>)

-   外部表和内部表的总数必须小于连接中允许的最大表数。

-   子查询可以是相关的或不相关的。在 MySQL 8.0.16 及更高版本中，去相关查看`在哪里`子查询的子句用作参数`存在`, 并使其可以像在内部使用一样对其进行优化`输入（从...中选择 b）`.术语*平凡相关*意味着谓词是等式谓词，它是唯一的谓词`在哪里`子句（或与`和`)，并且一个操作数来自子查询中引用的表，而另一个操作数来自外部查询块。

-   这`清楚的`关键字被允许但被忽略。半连接策略自动处理重复删除。

-   一种`通过...分组`允许但忽略子句，除非子查询还包含一个或多个聚合函数。

-   一个`订购方式`子句被允许但被忽略，因为排序与半连接策略的评估无关。

    如果子查询满足上述条件，MySQL 会将其转换为半连接（或者，在 MySQL 8.0.17 或更高版本中，如果适用，则为反连接）并从以下策略中做出基于成本的选择：

-   将子查询转换为联接，或使用表拉出并将查询作为子查询表和外部表之间的内部联接运行。表拉出将表从子查询拉出到外部查询。

    [](<>)

-   *重复清除*: 像连接一样运行半连接，并使用临时表删除重复记录。

    [](<>)

-   *第一场比赛*：当扫描内部表的行组合并且给定值组有多个实例时，选择一个而不是全部返回。这种“快捷方式”扫描并消除了不必要的行的产生。

    [](<>)

-   *松散扫描*：使用索引扫描子查询表，该索引允许从每个子查询的值组中选择单个值。

    [](<>)

-   将子查询具体化为用于执行连接的索引临时表，其中索引用于删除重复项。稍后在将临时表与外部表连接时，该索引也可能用于查找；如果不是，则扫描该表。有关具体化的更多信息，请参阅[第 8.2.2.2 节，“通过实现优化子查询”](subquery-materialization.html).

     [](<>)

    可以使用以下方法启用或禁用这些策略中的每一个[`优化器开关`](server-system-variables.html#sysvar_optimizer_switch)系统变量标志：

-   这[`半连接`](switchable-optimizations.html#optflag_semijoin)flag 控制是否使用半连接。从 MySQL 8.0.17 开始，这也适用于反连接。

-   如果[`半连接`](switchable-optimizations.html#optflag_semijoin)启用时，[`第一场比赛`](switchable-optimizations.html#optflag_firstmatch),[`松散扫描`](switchable-optimizations.html#optflag_loosescan),[`重复除草`](switchable-optimizations.html#optflag_duplicateweedout)， 和[`物化`](switchable-optimizations.html#optflag_materialization)标志可以更好地控制允许的半连接策略。

-   如果[`重复除草`](switchable-optimizations.html#optflag_duplicateweedout)semijoin 策略被禁用，除非所有其他适用的策略也被禁用，否则不会使用它。

-   如果[`重复除草`](switchable-optimizations.html#optflag_duplicateweedout)被禁用，有时优化器可能会生成一个远非最佳的查询计划。这是由于贪婪搜索期间的启发式修剪而发生的，可以通过设置来避免这种情况[`optimizer_prune_level=0`](server-system-variables.html#sysvar_optimizer_prune_level).

    默认情况下启用这些标志。看[第 8.9.2 节，“可切换的优化”](switchable-optimizations.html).

    优化器将视图和派生表的处理差异降至最低。这会影响使用`STRAIGHT_JOIN`修饰符和一个视图`在`可以转换为半连接的子查询。以下查询说明了这一点，因为处理中的变化会导致转换发生变化，从而导致不同的执行策略：


```
CREATE VIEW v AS
SELECT *
FROM t1
WHERE a IN (SELECT b
           FROM t2);

SELECT STRAIGHT_JOIN *
FROM t3 JOIN v ON t3.x = v.a;
```

优化器首先查看视图并将`在`子查询到半连接，然后检查是否可以将视图合并到外部查询中。因为`STRAIGHT_JOIN`外部查询中的修饰符阻止半连接，优化器拒绝合并，导致派生表评估使用物化表。

[`解释`](explain.html)输出表明使用了如下半连接策略：

-   对于扩展[`解释`](explain.html)输出，由以下显示的文本[`显示警告`](show-warnings.html)显示了重写的查询，它显示了半连接结构。（看[第 8.8.3 节，“扩展 EXPLAIN 输出格式”](explain-extended.html).) 从中您可以了解哪些表已从半连接中拉出。如果一个子查询被转换为一个半连接，你应该看到子查询谓词消失了，它的表和`在哪里`子句被合并到外部查询连接列表中，并且`在哪里`条款。

-   Duplicate Weedout 的临时表使用由`临时开始`和`暂时结束`在里面`额外的`柱子。未被拉出且在范围内的表[`解释`](explain.html)覆盖的输出行`临时开始`和`暂时结束`有他们的`行号`在临时表中。

-   `第一场比赛（*`tbl_name`*)`在里面`额外的`列表示加入快捷方式。

-   `松散扫描（*`米`*..*`n`*)`在里面`额外的`列表示使用 LooseScan 策略。*`米`*和*`n`*是关键零件编号。

-   用于实现的临时表由带有`选择类型`的价值`物化`和行`桌子`的价值`<子查询*`ñ`*>`.

    在 MySQL 8.0.21 及更高版本中，半连接转换也可以应用于单表[`更新`](update.html)要么[`删除`](delete.html)使用 a 的语句`[不] 在`要么`[不存在`子查询谓词，前提是语句不使用`订购方式`要么`限制`，并且优化器提示或[`优化器开关`](server-system-variables.html#sysvar_optimizer_switch)环境。
