本篇已收录在 MySQL 是怎样运行的 学习笔记系列
条件化简
我们编写的查询语句的搜索条件本质上是一个表达式,这些表达式可能比较繁杂,或者不能高效的执行,MySQL的查询优化器会为我们简化这些表达式。
移除不必要的括号
有时候表达式里有许多无用的括号,比如这样:
((a = 5 AND b = c) OR ((a > c) AND (c < 5)))
看着就很烦,优化器会把那些用不到的括号给干掉,就是这样:
(a = 5 and b = c) OR (a > c AND c < 5)
常量传递(constant_propagation)
有时候某个表达式是某个列和某个常量做等值匹配,比如这样:
a = 5
当这个表达式和其他涉及列a的表达式使用AND连接起来时,可以将其他表达式中的a的值替换为5,比如这样:
a = 5 AND b > a
就可以被转换为:
a = 5 AND b > 5
移除没用的条件(trivial_condition_removal)
对于一些明显永远为TRUE或者FALSE的表达式,优化器会移除掉它们,比如这个表达式:
(a < 1 and b = b) OR (a = 6 OR 5 != 5)
优化后为:
a < 1 OR a = 6
外连接消除
内连接的驱动表和被驱动表的位置可以相互转换,而左(外)连接和右(外)连接的驱动表和被驱动表是固定的。这就导致内连接可能通过优化表的连接顺序来降低整体的查询成本,而外连接却无法优化表的连接顺序。
如果能够将外连接转换为内连接, 则 mysql 的查询优化器就可以使用内连接的一些优化措施进行优化. 我们了解一下什么情况下外连接的查询语句可以转换成为内连接.比如下面的 sql 语句:
SELECT * FROM t1 LEFT JOIN t2 ON t1.m1 = t2.m2 WHERE t2.n2 IS NOT NULL;
在这种情况下:外连接和内连接也就没有什么区别了. 当然,我们也可以不用显式的指定被驱动表的某个列IS NOT NULL,只要隐含的有这个意思就行了,比方说这样:
SELECT * FROM t1 LEFT JOIN t2 ON t1.m1 = t2.m2 WHERE t2.m2 = 2;
所以上边的这个左(外)连接查询其实和下边这个内连接查询是等价的:
SELECT * FROM t1 INNER JOIN t2 ON t1.m1 = t2.m2 WHERE t2.m2 = 2;
我们把这种在外连接查询中,指定的WHERE子句中包含被驱动表中的列不为NULL值的条件称之为空值拒绝(英文名:reject-NULL)。在被驱动表的WHERE子句符合空值拒绝的条件后,外连接和内连接可以相互转换。这种转换带来的好处就是查询优化器可以通过评估表的不同连接顺序的成本,选出成本最低的那种连接顺序来执行查询。
子查询优化
子查询分为:
标量子查询
行子查询
列子查询
表子查询
按与外层查询关系来区分子查询:
不相关子查询
相关子查询
子查询语法注意事项
- 子查询必须用小括号扩起来。
- 在SELECT子句中的子查询必须是标量子查询。
- 在想要得到标量子查询或者行子查询,但又不能保证子查询的结果集只有一条记录时,应该使用LIMIT 1语句来限制记录数量。
- 对于[NOT] IN/ANY/SOME/ALL子查询来说,子查询中不允许有LIMIT语句。
- 子查询的结果其实就相当于一个集合,集合里的值排不排序一点儿都不重要,比如下边这个语句中的ORDER BY子句简直就是画蛇添足
- 集合里的值去不去重也没啥意义
- 不允许在一条语句中增删改某个表的记录时同时还对该表进行子查询
IN子查询优化
对于不相关的IN子查询,比如这样:
SELECT * FROM s1 WHERE key1 IN (SELECT common_field FROM s2 WHERE key3 = 'a');
不直接将不相关子查询的结果集当作外层查询的参数,而是将该结果集写入一个临时表里。
- 该临时表的列就是子查询结果集中的列。
- 写入临时表的记录会被去重。
- 一般情况下子查询结果集不会大的离谱,所以会为它建立基于内存的使用Memory存储引擎的临时表,而且会为该表建立哈希索引。(如果子查询的结果集非常大,超过了系统变量tmp_table_size或者max_heap_table_size,临时表会转而使用基于磁盘的存储引擎来保存结果集中的记录,索引类型也对应转变为B+树索引。)
当我们把子查询进行物化之后,假设子查询物化表的名称为materialized_table,该物化表存储的子查询结果集的列为m_val, 也就是说其实上边的查询就相当于表s1和子查询物化表materialized_table进行内连接:
SELECT s1.* FROM s1 INNER JOIN materialized_table ON key1 = m_val;
能不能不进行物化操作直接把子查询转换为连接呢?大家有兴趣可以了解了解semi-join