原文地址:https://dev.mysql.com/doc/refman/5.7/en/condition-filtering.html
译文:
8.2.1.12 条件过滤
在连接处理中,前缀行是连接中从一个表传递到另一个表的行。通常,优化器试图将前缀计数较低的表放在连接的早期,以防止行组合的数量快速增长。优化器可以使用关于从一个表中选择并传递到下一个表中的行条件的信息,因此它可以更准确地计算行估计并选择最佳执行计划。
如果没有条件过滤,表的前缀行计数将基于where子句根据优化器选择的任何访问方法查询出的估计行数。条件过滤使优化器能够在where子句中使用访问方法没有考虑到的其他相关条件,从而改进其前缀行数估计。例如,尽管可能会有一个基于索引的访问方法可以用来从连接中的当前表查询行,也可能在where子句中有表的附加条件可以用来过滤(进一步限制)传递到下一个表的符合条件的行的估计值。
只有满足下列任意一种情况的条件才有利于过滤估计:
1)它引用当前表;
2)它依赖于一个常量值或连接序列中较早表中的值;
3)访问方法还没有考虑到这一条件。
在explain的输出结果中,rows列表示所选访问方法的行估计,filtered列反应条件过滤的效果。filtered列的值用百分数表示。最大值是100,意味着没有行被过滤掉。从100逐渐减小的值表示被过滤掉的行数在增加。
前缀行计数(在连接中从当前表传递到下一个表的行数)是rows列值和filtered列值的乘积。也就是说,前缀行计数是估算的行计数,因条件过滤过滤而减小。例如,如果rows的值是1000,filtered的值是20%,条件过滤把行估计由1000减少到前缀行计数1000×20%=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';
假设数据集有如下所示特性:
1)employee表有1024行;
2)department表有12行;
3)每个表在dept_no列上都建有索引;
4)employee表在first_name列上建有索引;
5)满足employee.first_name条件的数据有8行;
employee.first_name = 'John'
6)满足employee.hire_date条件的数据有150行:
employee.hire_date BETWEEN '2018-01-01' AND '2018-06-01'
满足所有的条件的数据只有一行:
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索引上的访问方法检索出了与名字'Jhon'匹配的8行数据。没有过滤(filtered值是100%),所以所有行都是下一个表的前缀行:前缀行的计数rows
× filtered
= 8 × 100% = 8。
使用条件过滤时,优化器优化器还会考虑访问方法没有考虑的来自where子句的条件。在这种情况下,优化器使用启发法估计出employee.hire_date上的between条件的过滤效果为16.31%。因此,expalin会产生如下输出:
+----+------------+--------+------------------+---------+---------+------+----------+
| 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出现了一个例外:为了提供更多信息,对所有连接的表(包括最后一个表)计算过滤效果。
要控制优化器是否考虑其他过滤条件,可以使用系统变量optimizer_switch的condition_fanout_filter标志(参考Section 8.9.3, “Switchable Optimizations”)。这个标志在默认情况下是启用的,但是可以禁用它来抑制条件过滤(例如,如果发现某个特定查询在没有条件过滤的情况下可以获得更好的性能)。
如果优化器高估了条件过滤的效果,那么性能可能比不使用条件过滤的情况更差。在这种情况下,这些技术可能会有帮助:
1)如果一个列没有被索引,那么对它进行索引,这样优化器就可以获得关于列值分布的一些信息,并可以改进它的行估计。
2)更改连接顺序。实现这一点的方法包括连接顺序优化器提示(参考Section 8.9.2, “Optimizer Hints”),STRAIGHT_JOIN紧跟着SELECT和STRAIGHT_JOIN连接操作符。
3)禁用会话的条件过滤:
SET optimizer_switch = 'condition_fanout_filter=off';
PS:由于水平有限,译文中难免存在谬误,欢迎批评指正。