Documentation Home
MySQL 8.3 Reference Manual
Related Documentation Download this Manual
PDF (US Ltr) - 40.8Mb
PDF (A4) - 40.9Mb
Man Pages (TGZ) - 294.0Kb
Man Pages (Zip) - 409.0Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb
Excerpts from this Manual

10.2.1.13 条件过滤

在连接处理中,前缀行是从一个表传递到下一个表的行。在一般情况下,优化器尝试将表的前缀计数尽量少地放置在连接顺序的早期,以避免行组合的数量快速增加。只要优化器可以使用从一个表中选择的行的条件信息,并将其传递到下一个表,优化器就可以更准确地计算行估算和选择最佳执行计划。

如果没有条件过滤,表的前缀行计数基于WHERE子句中估算的行数,根据优化器选择的访问方法。条件过滤使优化器能够使用WHERE子句中的其他相关条件,而不是由访问方法考虑的,从而改进其前缀行计数估算。例如,即使有一个基于索引的访问方法可以从当前表中选择行,但WHERE子句中可能还有其他条件可以进一步限制估算的行数,并将其传递到下一个表。

只有满足以下条件的条件才会贡献于过滤估算:

  • 它引用当前表。

  • 它依赖于早期表中的常量值。

  • 它没有被访问方法考虑。

EXPLAIN输出中,rows列指示了所选访问方法的行估算,而filtered列反映了条件过滤的效果。filtered值以百分比表示。最大值为100,表示没有行被过滤。从100递减的值表示增加的过滤量。

前缀行计数(从当前表传递到下一个表的估算行数)是rowsfiltered值的乘积。也就是说,前缀行计数是估算的行数,减少了估算的过滤效果。例如,如果rows是1000,而filtered是20%,那么条件过滤将估算的行数从1000减少到1000 × 20% = 1000 × .2 = 200。

考虑以下查询:

SELECT *
  FROM employee JOIN department ON employee.dept_no = department.dept_no
  WHERE employee.first_name = 'John'
  AND employee.hire_date BETWEEN '2018-01-01' AND '2018-06-01';

假设数据集具有以下特征:

  • 员工表有1024行。

  • 部门表有12行。

  • 两个表都有dept_no索引。

  • 员工表还有first_name索引。

  • 8行满足员工.first_name条件:

    employee.first_name = 'John'
  • 150行满足员工.hire_date条件:

    employee.hire_date BETWEEN '2018-01-01' AND '2018-06-01'
  • 1行同时满足这两个条件:

    employee.first_name = 'John'
    AND employee.hire_date BETWEEN '2018-01-01' AND '2018-06-01'

如果没有条件过滤,EXPLAIN将产生类似的输出:

+----+------------+--------+------------------+---------+---------+------+----------+
| id | table      | type   | possible_keys    | key     | ref     | rows | filtered |
+----+------------+--------+------------------+---------+---------+------+----------+
| 1  | employee   | ref    | name,h_date,dept | name    | const   | 8    | 100.00   |
| 1  | department | eq_ref | PRIMARY          | PRIMARY | dept_no | 1    | 100.00   |
+----+------------+--------+------------------+---------+---------+------+----------+

对于 employee,在 name 索引上的访问方法选择了 8 行,匹配名称为 'John'。没有进行过滤(filtered 为 100%),因此所有行都是下一个表的前缀行:前缀行计数是 rows × filtered = 8 × 100% = 8。

使用条件过滤时,优化器还考虑了 WHERE 子句中未被访问方法考虑的条件。在这种情况下,优化器使用启发式方法来估算 employee.hire_date 上的 BETWEEN 条件的过滤效果为 16.31%。因此,EXPLAIN 产生的输出如下:

+----+------------+--------+------------------+---------+---------+------+----------+
| id | table      | type   | possible_keys    | key     | ref     | rows | filtered |
+----+------------+--------+------------------+---------+---------+------+----------+
| 1  | employee   | ref    | name,h_date,dept | name    | const   | 8    | 16.31    |
| 1  | department | eq_ref | PRIMARY          | PRIMARY | dept_no | 1    | 100.00   |
+----+------------+--------+------------------+---------+---------+------+----------+

现在前缀行计数是 rows × filtered = 8 × 16.31% = 1.3,这更好地反映了实际数据集。

通常,优化器不会计算最后一个连接表的条件过滤效果(前缀行计数减少),因为没有下一个表来传递行。一个例外情况是 EXPLAIN:为了提供更多信息,优化器计算了所有连接表的过滤效果,包括最后一个表。

要控制优化器是否考虑额外的过滤条件,请使用 condition_fanout_filter 标志的 optimizer_switch 系统变量(见 第 10.9.2 节,“可切换优化”)。该标志默认启用,但可以禁用以抑制条件过滤(例如,如果某个查询在不使用时性能更好)。

如果优化器高估了条件过滤的效果,性能可能比不使用条件过滤时更差。在这种情况下,以下技术可能有助:

  • 如果某列未索引,请索引它,以便优化器拥有该列值分布的信息并可以改进行估计。

  • 类似地,如果没有列直方图信息,请生成直方图(见 第 10.9.6 节,“优化器统计信息”)。

  • 更改连接顺序。实现方式包括连接顺序优化器提示(见 第 10.9.3 节,“优化器提示”)、STRAIGHT_JOIN紧跟在 SELECT 之后,以及 STRAIGHT_JOIN 连接运算符。

  • 禁用会话中的条件过滤:

    SET optimizer_switch = 'condition_fanout_filter=off';

    或者,对于给定的查询,使用优化器提示:

    SELECT /*+ SET_VAR(optimizer_switch = 'condition_fanout_filter=off') */ ...