Mysql SQL优化


在数据库实现早期,查询优化器对子查询一般采用嵌套执行的方式,即对父查询中的每一行,都执行一次子查询,这样子查询会执行很多次。这种执行方式效率很低。


而对子查询进行优化,可能带来几个数量级的查询效率的提高。
子查询转变成为连接操作之后,会得到如下好处:
1 子查询不用执行很多次。
2 优化器可以根据统计信息来选择不同的连接方法和不同的连接顺序。


子查询中的连接条件、过滤条件分别变成了父查询的连接条件、过滤条件,优化器可以对这些条件进行下推,以提高执行效率。




这条sql之所以出现的性能问题在于mysql优化器在处理子查询的弱点,mysql优化器在处理子查询的时候,会将将子查询改写。通常情况下,我们希望由内到外,先完成子查询的结果,然后在用子查询来驱动外查询的表,完成查询;但是mysql处理为将会先扫描外面表中的所有数据,每条数据将会传到子查询中与子查询关联,如果外表很大的话,那么性能上将会出现问题;

1 相关子查询。
子查询的执行依赖于外层父查询的一些属性值。子查询因依赖于父查询的参数,当父查询的参数改变时,子查询需要根据新参数值重新执行(查询优化器对相关子查询进行优化有一定意义),如:


SELECT * FROM t1 WHERE col_1 = ANY
    (SELECT col_1 FROM t2 WHERE t2.col_2 = t1.col_2);
/* 子查询语句中存在父查询的t1表的col_2列 */

执行次为:t1=100,t2=100,次数为100*100

2 非相关子查询。


子查询的执行,不依赖于外层父查询的任何属性值。这样子查询具有独立性,可独自求解,形成一个子查询计划先于外层的查询求解,如:


SELECT * FROM t1 WHERE col_1 = ANY
    (SELECT col_1 FROM t2 WHERE t2.col_2 = 10);
//子查询语句中(t2)不存在父查询(t1)的属性

http://blog.163.com/li_hx/blog/static/183991413201421075949661/


1 子查询合并(Subquery Coalescing)。
在某些条件下(语义等价:两个查询块产生同样的结果集),多个子查询能够合并成一个子查询(合并后还是子查询,以后可以通过其他技术消除掉子查询)。这样可以把多次表扫描、多次连接减少为单次表扫描和单次连接,如:


SELECT * FROM t1 WHERE a1<10 AND (
    EXISTS (SELECT a2 FROM t2 WHERE t2.a2<5 AND t2.b2=1) OR 
    EXISTS (SELECT a2 FROM t2 WHERE t2.a2<5 AND t2.b2=2) 
);


可优化为:


SELECT * FROM t1 WHERE a1<10 AND (
    EXISTS (SELECT a2 FROM t2 WHERE t2.a2<5 AND (t2.b2=1 OR t2.b2=2) 
    /*两个ESISTS子句合并为一个,条件也进行了合并 */
);

2 子查询展开(Subquery Unnesting)。
又称子查询反嵌套,又称为子查询上拉。
把一些子查询置于外层的父查询中,作为连接关系与外层父查询并列,其实质是把某些子查询重写为等价的多表连接操作(展开后,子查询不存在了,外部查询变成了多表连接)。


带来的好处是,有关的访问路径、连接方法和连接顺序可能被有效使用,使得查询语句的层次尽可能的减少。


常见的IN/ANY/SOME/ALL/EXISTS依据情况转换为半连接(SEMI JOIN)、普通类型的子查询消除等情况属于此类,如:


SELECT * FROM t1, (SELECT * FROM t2 WHERE t2.a2 >10) v_t2 
WHERE t1.a1<10 AND v_t2.a2<20;


可优化为:


SELECT * FROM t1, t2 WHERE t1.a1<10 AND t2.a2<20 AND t2.a2 >10; 
/* 子查询变为了t1、t2表的连接操作,相当于把t2表从子查询中上拉了一层 */

MySQL可以优化什么格式的子查询?
MySQL可以优化什么格式的子查询?

4.
MySQL支持对简单SELECT查询中的子查询优化,包括:
简单SELECT查询中的子查询。
带有DISTINCT、ORDERBY、LIMIT操作的简单SELECT查询中的子查询。


CREATE TABLE t1 (a1 INT, b1 INT, PRIMARY KEY (a1));
CREATE TABLE t2 (a2 INT, b2 INT, PRIMARY KEY (a2));
CREATE TABLE t3 (a3 INT, b3 INT, PRIMARY KEY (a3));
插入10000行与上例同样的数据。


查询执行计划如下:
mysql> EXPLAIN EXTENDED SELECT * FROM t1 WHERE t1.a1<100 AND a1 IN (SELECT a2 FROM t2 WHERE t2.a2 >10);
+----+-------------+-------+--------+---------+--------------------------+
| id | select_type | table | type   | key     | Extra                    |
+----+-------------+-------+--------+---------+--------------------------+
|  1 | SIMPLE      | t2    | range  | PRIMARY | Using where; Using index |
|  1 | SIMPLE      | t1    | eq_ref | PRIMARY | NULL                     |
+----+-------------+-------+--------+---------+--------------------------+
2 rows in set, 1 warning (0.00 sec)
该子查询被优化器优化了,子查询被拉到父查询进行连接    


MySQL不支持对如下情况的子查询进行优化:
带有UNION操作。
带有GROUPBY、HAVING、聚集函数。
使用ORDERBY中带有LIMIT。
内表、外表的个数超过MySQL支持的最大表的连接数,最多支持63张表连接


聚集函数操作在子查询中,查询执行计划如下:


mysql> EXPLAIN EXTENDED SELECT * FROM t1 WHERE t1.a1>(SELECT MIN(t2.a2) FROM t2);
+----+-------------+-------+------+---------------+------------------------------+
| id | select_type | table | type | possible_keys | Extra                        |
+----+-------------+-------+------+---------------+------------------------------+
|  1 | PRIMARY     | t1    | ALL  | t_idx_t1      | Using where                  |
|  2 | SUBQUERY    | NULL  | NULL | NULL          | Select tables optimized away |
+----+-------------+-------+------+---------------+------------------------------+

该子查询不支持进一步优化、


1 子查询合并技术,不支持:--例1


mysql> explain EXTENDED SELECT * FROM t1 WHERE a1<4 AND (EXISTS (SELECT a2 FROM t2 WHERE t2.a2<5 AND t2.b2=1) OR EXISTS (SELECT a2 FROM t2 WHERE t2.a2<5 AND t2.b2=2) );


+----+-------------+-------+-------+---------------+------------------------------------+
| id | select_type | table | type  | possible_keys | Extra                              |
+----+-------------+-------+-------+---------------+------------------------------------+
|  1 | PRIMARY     | t1    | range | t_idx_t1      | Using index condition              |
|  3 | SUBQUERY    | t2    | range | t_idx_t2      | Using index condition; Using where |
|  2 | SUBQUERY    | t2    | range | t_idx_t2      | Using index condition; Using where |
+----+-------------+-------+-------+---------------+------------------------------------+

t2表上执行了2次子查询
如果支持子查询合并技术,则t2表上只执行一次子查询

1 子查询合并技术,不支持:--例1


SQL语句的语义等价于上一条SQL语句,查询执行计划如下:


mysql> EXPLAIN EXTENDED SELECT * FROM t1 WHERE a1<10 AND EXISTS (SELECT a2 FROM t2 WHERE t2.a2<5 AND (t2.b2=1 OR t2.b2=2));


+----+-------------+-------+------+------+-------------+
| id | select_type | table | type | key  | Extra       |
+----+-------------+-------+------+------+-------------+
|  1 | PRIMARY     | t1    | ALL  | NULL | Using where |
|  2 | SUBQUERY    | t2    | ALL  | NULL | Using where |
+----+-------------+-------+------+------+-------------+
2 rows in set, 1 warning (0.02 sec)


人为的合并查询条件为“(t2.b2=1 OR t2.b2=2)”
t2表上的子查询,只执行一次

2 子查询展开(子查询反嵌套)技术,支持得不够好:--例2


mysql> EXPLAIN EXTENDED SELECT * FROM t1, (SELECT * FROM t2 WHERE t2.a2 >10) v_t2 WHERE t1.a1<10 AND v_t2.a2<20;


+----+-------------+------------+------+------+----------------------------------------------------+
| id | select_type | table      | type | key  | Extra |
+----+-------------+------------+------+------+----------------------------------------------------+
|  1 | PRIMARY     | <derived2> | ALL  | NULL | Using where |
|  1 | PRIMARY     | t1         | ALL  | NULL | Using where; Using join buffer (Block Nested Loop) |
|  2 | DERIVED     | t2         | ALL  | NULL | Using where |
+----+-------------+------------+------+------+----------------------------------------------------+
3 rows in set, 1 warning (0.00 sec)

从查询执行计划看,在表t2上的子查询被单独执行,
没和表t1进行了嵌套循环连接,子查询没有被消除,
所以MySQL支持子查询反嵌套技术有限

t2是一个被驱动的表,t2被单独执行了,然后与t1嵌套循环连接

2 子查询展开(子查询反嵌套)技术,支持得不够好:--例3


再看一个IN子查询的例子,查询执行计划如下:
mysql> EXPLAIN EXTENDED SELECT * FROM t1 WHERE t1.a1<100 AND a1 IN (SELECT a2 FROM t2 WHERE t2.a2 >10);
+----+--------------+-------------+------+------+----------------------------------------------------+
| id | select_type  | table       | type | key  | Extra         |
+----+--------------+-------------+------+------+----------------------------------------------------+
|  1 | SIMPLE       | <subquery2> | ALL  | NULL | Using where   |
|  1 | SIMPLE       | t1          | ALL  | NULL | Using where; Using join buffer (Block Nested Loop) |
|  2 | MATERIALIZED | t2          | ALL  | NULL | Using where   |
+----+--------------+-------------+------+------+----------------------------------------------------+
3 rows in set, 1 warning (0.00 sec)

从查询执行计划看,在表t2上的子查询被物化
从查询执行计划看,子查询不存在,表t1t2直接做了块嵌套循环半连接(Block Nested Loop),把子查询上拉到父查询中用嵌套循环半连接完成IN操作。另外,由于子查询上拉,使得增加连接条件“a1=a2”,而原先的条件“a2=10”可以利用常量传递优化技术,使得“a1=a2=10”,所以查询执行计划中,两个索引扫描的条件分别为:a1 = 10a2 = 10


2 子查询展开(子查询反嵌套)技术,支持得不够好:--例4


重新构造表如下,与上面示例不同之处在于这里所用列是主键列。


CREATE TABLE t1 (a1 INT, b1 INT, PRIMARY KEY (a1));
CREATE TABLE t2 (a2 INT, b2 INT, PRIMARY KEY (a2));
CREATE TABLE t3 (a3 INT, b3 INT, PRIMARY KEY (a3));
插入10000行与上例同样的数据



执行与例3同样的查询语句,查询执行计划如下:


mysql> EXPLAIN EXTENDED SELECT * FROM t1 WHERE t1.a1<100 AND a1 IN (SELECT a2 FROM t2 WHERE t2.a2 >10);


+----+-------------+-------+--------+---------+--------------------------+
| id | select_type | table | type   | key     | Extra                    |
+----+-------------+-------+--------+---------+--------------------------+
|  1 | SIMPLE      | t2    | range  | PRIMARY | Using where; Using index |
|  1 | SIMPLE      | t1    | eq_ref | PRIMARY | NULL                     |
+----+-------------+-------+--------+---------+--------------------------+
2 rows in set, 1 warning (0.00 sec)

从查询执行计划看,子查询不存在,SQL语句被转换为内连接操作,
这表明MySQL只有在针对主键列进行类似的子查询时,
才把子查询上拉为内连接。
所以,MySQL还是支持子查询展开技术的。

3 聚集子查询消除、技术,不支持:--例5


mysql> EXPLAIN EXTENDED SELECT * FROM t1 WHERE t1.a1>(SELECT MIN(t2.a2) FROM t2);
+----+-------------+-------+------+---------------+------------------------------+
| id | select_type | table | type | possible_keys | Extra                        |
+----+-------------+-------+------+---------------+------------------------------+
|  1 | PRIMARY     | t1    | ALL  | t_idx_t1      | Using where                  |
|  2 | SUBQUERY    | NULL  | NULL | NULL          | Select tables optimized away |
+----+-------------+-------+------+---------------+------------------------------+







请使用浏览器的分享功能分享到微信等