在数据库实现早期,查询优化器对子查询一般采用嵌套执行的方式,即对父查询中的每一行,都执行一次子查询,这样子查询会执行很多次。这种执行方式效率很低。
而对子查询进行优化,可能带来几个数量级的查询效率的提高。
子查询转变成为连接操作之后,会得到如下好处:
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列 */
2 非相关子查询。
子查询的执行,不依赖于外层父查询的任何属性值。这样子查询具有独立性,可独自求解,形成一个子查询计划先于外层的查询求解,如:
SELECT * FROM t1 WHERE col_1 = ANY
(SELECT col_1 FROM t2 WHERE t2.col_2 = 10);
//子查询语句中(t2)不存在父查询(t1)的属性
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子句合并为一个,条件也进行了合并 */
);
又称子查询反嵌套,又称为子查询上拉。
把一些子查询置于外层的父查询中,作为连接关系与外层父查询并列,其实质是把某些子查询重写为等价的多表连接操作(展开后,子查询不存在了,外部查询变成了多表连接)。
带来的好处是,有关的访问路径、连接方法和连接顺序可能被有效使用,使得查询语句的层次尽可能的减少。
常见的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可以优化什么格式的子查询?
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张表连接
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表上只执行一次子查询
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)
没和表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)
从查询执行计划看,子查询不存在,表t1和t2直接做了块嵌套循环半连接(Block Nested Loop),把子查询上拉到父查询中用嵌套循环半连接完成IN操作。另外,由于子查询上拉,使得增加连接条件“a1=a2”,而原先的条件“a2=10”可以利用常量传递优化技术,使得“a1=a2=10”,所以查询执行计划中,两个索引扫描的条件分别为:a1 = 10、a2 = 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 |
+----+-------------+-------+------+---------------+------------------------------+
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)
这表明MySQL只有在针对主键列进行类似的子查询时,
才把子查询上拉为内连接。
所以,MySQL还是支持子查询展开技术的。
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 |
+----+-------------+-------+------+---------------+------------------------------+