#### 8.2.1.8 嵌套连接优化

表达连接的语法允许嵌套连接。下面的讨论是指连接语法中描述的[第 13.2.10.2 节，“JOIN 子句”](join.html).

的语法*`表因子`*与 SQL 标准相比得到了扩展。后者只接受*`表参考`*，而不是一对括号内的列表。如果我们考虑列表中的每个逗号，这是一个保守的扩展*`表参考`*相当于内部连接的项目。例如：

```
SELECT * FROM t1 LEFT JOIN (t2, t3, t4)
                 ON (t2.a=t1.a AND t3.b=t1.b AND t4.c=t1.c)
```

相当于：

```
SELECT * FROM t1 LEFT JOIN (t2 CROSS JOIN t3 CROSS JOIN t4)
                 ON (t2.a=t1.a AND t3.b=t1.b AND t4.c=t1.c)
```

在 MySQL 中，`交叉连接`在语法上等价于`内部联接`;他们可以互相替换。在标准 SQL 中，它们是不等价的。`内部联接`与一个`在`条款;`交叉连接`以其他方式使用。

通常，在仅包含内连接操作的连接表达式中可以忽略括号。考虑这个连接表达式：

```
t1 LEFT JOIN (t2 LEFT JOIN t3 ON t2.b=t3.b OR t2.b IS NULL)
   ON t1.a=t2.a
```

在删除括号并将操作分组到左侧之后，该连接表达式转换为以下表达式：

```
(t1 LEFT JOIN t2 ON t1.a=t2.a) LEFT JOIN t3
    ON t2.b=t3.b OR t2.b IS NULL
```

然而，这两种表达方式并不等价。要看到这一点，假设表`t1`, `t2`， 和`t3`有以下状态：

-   桌子`t1`包含行`(1)`, `(2)`

-   桌子`t2`包含行`(1,101)`

-   桌子`t3`包含行`(101)`

    在这种情况下，第一个表达式返回一个包含行的结果集`(1,1,101,101)`, `(2,NULL,NULL,NULL)`，而第二个表达式返回行`(1,1,101,101)`, `(2,NULL,NULL,101)`：


```
mysql> SELECT *
       FROM t1
            LEFT JOIN
            (t2 LEFT JOIN t3 ON t2.b=t3.b OR t2.b IS NULL)
            ON t1.a=t2.a;
+------+------+------+------+
| a    | a    | b    | b    |
+------+------+------+------+
|    1 |    1 |  101 |  101 |
|    2 | NULL | NULL | NULL |
+------+------+------+------+

mysql> SELECT *
       FROM (t1 LEFT JOIN t2 ON t1.a=t2.a)
            LEFT JOIN t3
            ON t2.b=t3.b OR t2.b IS NULL;
+------+------+------+------+
| a    | a    | b    | b    |
+------+------+------+------+
|    1 |    1 |  101 |  101 |
|    2 | NULL | NULL |  101 |
+------+------+------+------+
```

在以下示例中，外连接操作与内连接操作一起使用：

```
t1 LEFT JOIN (t2, t3) ON t1.a=t2.a
```

该表达式不能转换为以下表达式：

```
t1 LEFT JOIN t2 ON t1.a=t2.a, t3
```

对于给定的表状态，两个表达式返回不同的行集：

```
mysql> SELECT *
       FROM t1 LEFT JOIN (t2, t3) ON t1.a=t2.a;
+------+------+------+------+
| a    | a    | b    | b    |
+------+------+------+------+
|    1 |    1 |  101 |  101 |
|    2 | NULL | NULL | NULL |
+------+------+------+------+

mysql> SELECT *
       FROM t1 LEFT JOIN t2 ON t1.a=t2.a, t3;
+------+------+------+------+
| a    | a    | b    | b    |
+------+------+------+------+
|    1 |    1 |  101 |  101 |
|    2 | NULL | NULL |  101 |
+------+------+------+------+
```

因此，如果我们在带有外连接运算符的连接表达式中省略括号，我们可能会更改原始表达式的结果集。

更准确地说，我们不能忽略左外连接操作的右操作数和右连接操作的左操作数中的括号。换句话说，我们不能忽略外连接操作的内表表达式的括号。可以忽略其他操作数（外部表的操作数）的括号。

以下表达式：

```
(t1,t2) LEFT JOIN t3 ON P(t2.b,t3.b)
```

等效于任何表的此表达式`t1,t2,t3`和任何条件`磷`过属性`t2.b`和`t3.b`：

```
t1, t2 LEFT JOIN t3 ON P(t2.b,t3.b)
```

每当连接表达式中连接操作的执行顺序 (*`加入表`*) 不是从左到右，我们说的是嵌套连接。考虑以下查询：

```
SELECT * FROM t1 LEFT JOIN (t2 LEFT JOIN t3 ON t2.b=t3.b) ON t1.a=t2.a
  WHERE t1.a > 1

SELECT * FROM t1 LEFT JOIN (t2, t3) ON t1.a=t2.a
  WHERE (t2.b=t3.b OR t2.b IS NULL) AND t1.a > 1
```

这些查询被认为包含这些嵌套连接：

```
t2 LEFT JOIN t3 ON t2.b=t3.b
t2, t3
```

在第一个查询中，嵌套连接由左连接操作构成。在第二个查询中，它是通过内部连接操作形成的。

在第一个查询中，括号可以省略：连接表达式的语法结构规定了连接操作的相同执行顺序。对于第二个查询，括号不能省略，尽管此处的连接表达式可以在没有它们的情况下明确解释。在我们的扩展语法中，括号`(t2, t3)`的第二个查询是必需的，尽管理论上可以在没有它们的情况下解析查询：我们仍然会有查询的明确句法结构，因为`左连接`和`在`为表达式起左右分隔符的作用`(t2,t3)`.

前面的例子说明了以下几点：

-   对于只涉及内连接（而不是外连接）的连接表达式，可以删除括号并从左到右计算连接。事实上，表格可以按任何顺序进行评估。

-   通常，对于外部联接或外部联接与内部联接混合，情况并非如此。删除括号可能会改变结果。

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

具有嵌套外部联接的查询以与具有内部联接的查询相同的管道方式执行。更准确地说，利用了嵌套循环连接算法的变体。回想一下嵌套循环连接执行查询的算法（参见[第 8.2.1.7 节，“嵌套循环连接算法”](nested-loop-joins.html)）。假设一个连接查询超过 3 个表`T1,T2,T3`有这种形式：

```
SELECT * FROM T1 INNER JOIN T2 ON P1(T1,T2)
                 INNER JOIN T3 ON P2(T2,T3)
  WHERE P(T1,T2,T3)
```

这里，`P1(T1,T2)`和`P2(T3,T3)`是一些连接条件（在表达式上），而`P(T1,T2,T3)`是表列的条件`T1,T2,T3`.

嵌套循环连接算法将以下列方式执行此查询：

```
FOR each row t1 in T1 {
  FOR each row t2 in T2 such that P1(t1,t2) {
    FOR each row t3 in T3 such that P2(t2,t3) {
      IF P(t1,t2,t3) {
         t:=t1||t2||t3; OUTPUT t;
      }
    }
  }
}
```

符号`t1||t2||t3`表示通过连接行的列构造的行`t1`,`t2`， 和`t3`.在以下一些示例中，`空值`出现表名的位置表示其中的一行`空值`用于该表的每一列。例如，`t1||t2||NULL`表示通过连接行的列构造的行`t1`和`t2`， 和`空值`对于每一列`t3`.据说这样的一行`空值`-补充。

[](<>)

现在考虑一个带有嵌套外连接的查询：

```
SELECT * FROM T1 LEFT JOIN
              (T2 LEFT JOIN T3 ON P2(T2,T3))
              ON P1(T1,T2)
  WHERE P(T1,T2,T3)
```

对于此查询，修改嵌套循环模式以获得：

```
FOR each row t1 in T1 {
  BOOL f1:=FALSE;
  FOR each row t2 in T2 such that P1(t1,t2) {
    BOOL f2:=FALSE;
    FOR each row t3 in T3 such that P2(t2,t3) {
      IF P(t1,t2,t3) {
        t:=t1||t2||t3; OUTPUT t;
      }
      f2=TRUE;
      f1=TRUE;
    }
    IF (!f2) {
      IF P(t1,t2,NULL) {
        t:=t1||t2||NULL; OUTPUT t;
      }
      f1=TRUE;
    }
  }
  IF (!f1) {
    IF P(t1,NULL,NULL) {
      t:=t1||NULL||NULL; OUTPUT t;
    }
  }
}
```

通常，对于外连接操作中第一个内表的任何嵌套循环，都会引入一个标志，该标志在循环之前关闭并在循环之后进行检查。当外部表中的当前行与表示内部操作数的表匹配时，该标志被打开。如果在循环周期结束时标志仍然关闭，则没有为外部表的当前行找到匹配项。在这种情况下，该行由`空值`内表列的值。结果行被传递给输出的最终检查或下一个嵌套循环，但前提是该行满足所有嵌入式外部连接的连接条件。

在示例中，嵌入了由以下表达式表示的外连接表：

```
(T2 LEFT JOIN T3 ON P2(T2,T3))
```

对于带有内连接的查询，优化器可以选择不同的嵌套循环顺序，例如：

```
FOR each row t3 in T3 {
  FOR each row t2 in T2 such that P2(t2,t3) {
    FOR each row t1 in T1 such that P1(t1,t2) {
      IF P(t1,t2,t3) {
         t:=t1||t2||t3; OUTPUT t;
      }
    }
  }
}
```

对于带有外部连接的查询，优化器只能选择外部表循环在内部表循环之前的顺序。因此，对于我们的外连接查询，只有一种嵌套顺序是可能的。对于以下查询，优化器评估两个不同的嵌套。在两个嵌套中，`T1`必须在外循环中处理，因为它用于外连接。`T2`和`T3`在内连接中使用，因此连接必须在内循环中处理。但是，因为连接是内连接，`T2`和`T3`可以按任意顺序处理。

```
SELECT * T1 LEFT JOIN (T2,T3) ON P1(T1,T2) AND P2(T1,T3)
  WHERE P(T1,T2,T3)
```

一个嵌套评估`T2`， 然后`T3`：

```
FOR each row t1 in T1 {
  BOOL f1:=FALSE;
  FOR each row t2 in T2 such that P1(t1,t2) {
    FOR each row t3 in T3 such that P2(t1,t3) {
      IF P(t1,t2,t3) {
        t:=t1||t2||t3; OUTPUT t;
      }
      f1:=TRUE
    }
  }
  IF (!f1) {
    IF P(t1,NULL,NULL) {
      t:=t1||NULL||NULL; OUTPUT t;
    }
  }
}
```

另一个嵌套评估`T3`， 然后`T2`：

```
FOR each row t1 in T1 {
  BOOL f1:=FALSE;
  FOR each row t3 in T3 such that P2(t1,t3) {
    FOR each row t2 in T2 such that P1(t1,t2) {
      IF P(t1,t2,t3) {
        t:=t1||t2||t3; OUTPUT t;
      }
      f1:=TRUE
    }
  }
  IF (!f1) {
    IF P(t1,NULL,NULL) {
      t:=t1||NULL||NULL; OUTPUT t;
    }
  }
}
```

在讨论内连接的嵌套循环算法时，我们省略了一些对查询执行性能影响可能很大的细节。我们没有提到所谓的“下推”条件。假设我们的`在哪里`健康）状况`P(T1,T2,T3)`可以用合取公式表示：

```
P(T1,T2,T2) = C1(T1) AND C2(T2) AND C3(T3).
```

在这种情况下，MySQL 实际上使用以下嵌套循环算法来执行带有内连接的查询：

```
FOR each row t1 in T1 such that C1(t1) {
  FOR each row t2 in T2 such that P1(t1,t2) AND C2(t2)  {
    FOR each row t3 in T3 such that P2(t2,t3) AND C3(t3) {
      IF P(t1,t2,t3) {
         t:=t1||t2||t3; OUTPUT t;
      }
    }
  }
}
```

你会看到每个连词`C1(T1)`,`C2(T2)`,`C3(T3)`被推出最内层循环到最外层循环，在那里它可以被评估。如果`C1(T1)`是一个非常严格的条件，这个条件下推可能会大大减少表中的行数`T1`传递给内部循环。因此，查询的执行时间可能会大大提高。

对于带有外连接的查询，`在哪里`只有在发现外部表中的当前行与内部表匹配后，才检查条件。因此，从内部嵌套循环中推出条件的优化不能直接应用于具有外部连接的查询。在这里，我们必须引入条件下推谓词，这些谓词由遇到匹配时打开的标志保护。

回想一下这个带有外连接的例子：

```
P(T1,T2,T3)=C1(T1) AND C(T2) AND C3(T3)
```

例如，使用受保护的下推条件的嵌套循环算法如下所示：

```
FOR each row t1 in T1 such that C1(t1) {
  BOOL f1:=FALSE;
  FOR each row t2 in T2
      such that P1(t1,t2) AND (f1?C2(t2):TRUE) {
    BOOL f2:=FALSE;
    FOR each row t3 in T3
        such that P2(t2,t3) AND (f1&&f2?C3(t3):TRUE) {
      IF (f1&&f2?TRUE:(C2(t2) AND C3(t3))) {
        t:=t1||t2||t3; OUTPUT t;
      }
      f2=TRUE;
      f1=TRUE;
    }
    IF (!f2) {
      IF (f1?TRUE:C2(t2) && P(t1,t2,NULL)) {
        t:=t1||t2||NULL; OUTPUT t;
      }
      f1=TRUE;
    }
  }
  IF (!f1 && P(t1,NULL,NULL)) {
      t:=t1||NULL||NULL; OUTPUT t;
  }
}
```

一般来说，下推谓词可以从连接条件中提取，例如`P1(T1,T2)`和`P(T2,T3)`.在这种情况下，下推谓词也由一个标志保护，该标志防止检查谓词`空值`- 由相应的外连接操作生成的互补行。

如果它是由来自`在哪里`健康）状况。
