在连接处理中,前缀行是指从连接中的一个表传递到下一个表的那些行。一般来说,优化器会尝试将前缀行数较低的表放在连接顺序的前面,以防止行组合的数量迅速增加。在优化器能够使用有关从一个表中选择并传递到下一个表的行的条件信息的情况下,它就能够更准确地计算行估计并选择最佳执行计划。
如果没有条件过滤,表的行前缀数量将基于根据优化器选择的访问方法,通过 WHERE
子句估计的选定行数。条件过滤使优化器能够使用 WHERE
子句中其他相关的条件,而这些条件没有被访问方法考虑在内,从而改进其前缀行数估计。例如,即使可能存在基于索引的访问方法,该方法可用于从连接中的当前表中选择行,但 WHERE
子句中可能还存在该表的其他条件,可以过滤(进一步限制)传递到下一个表的合格行的估计值。
只有当条件满足以下条件时,它才会影响过滤估计值:
它引用当前表。
它依赖于连接序列中先前表中的常量值。
它没有被访问方法考虑在内。
在 EXPLAIN
输出中,rows
列指示所选访问方法的行估计值,而 filtered
列反映了条件过滤的影响。 filtered
值以百分比表示。最大值为 100,这意味着没有发生行过滤。从 100 递减的值表示过滤量的增加。
前缀行数(估计从连接中的当前表传递到下一个表的行数)是 rows
和 filtered
值的乘积。也就是说,前缀行数是估计的行数,减少了估计的过滤效果。例如,如果 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';
假设数据集具有以下特点
employee
表有 1024 行。department
表有 12 行。两张表都有
dept_no
列的索引。employee
表有first_name
列的索引。8 行满足此条件:
employee.first_name
employee.first_name = 'John'
150 行满足此条件:
employee.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 节,“优化器提示”)、紧跟在
SELECT
后的STRAIGHT_JOIN
,以及STRAIGHT_JOIN
连接运算符。禁用会话的条件过滤
SET optimizer_switch = 'condition_fanout_filter=off';
或者,对于给定的查询,使用优化器提示
SELECT /*+ SET_VAR(optimizer_switch = 'condition_fanout_filter=off') */ ...