昨天,开发人员报告,说测试机上一个sql突然变得很慢,sql如下:
select order0_.id as x0_0_
from order_tab order0_,
order_record_tab orderrecor1_,
order_factory_tab orderfacto2_,
 relate_order_tab relateorde3_
where (order0_.id=orderrecor1_.order_id )
          and(orderrecor1_.id=orderfacto2_.order_record_id )
          and(relateorde3_.order_id=order0_.id )
          and(orderfacto2_.id in(174028 , 174029))
          and((relateorde3_.relate_internet_order_id is not null ))
          and(relateorde3_.relate_internet_order_id not in
          (select order4_.internet_order_id
             from order_tab order4_,
                     order_record_tab orderrecor5_,
                     order_factory_tab orderfacto6_,
                     relate_order_tab relateorde7_
              where (order4_.id=orderrecor5_.order_id )
                       and(orderrecor5_.id=orderfacto6_.order_record_id )
                       and(relateorde7_.order_id=order4_.id )
                       and(orderfacto6_.id in(174028 , 174029))
                       and((relateorde7_.relate_internet_order_id is not null )
                )
              )
           );
   此sql故障时的执行计划如下:
+----+--------------------+--------------+-------------+------------------------------------------------------------------------------+-----------------------------+---------+----------------------------------------+------+-------------+
| id | select_type        | table        | type        | possible_keys                                                                | key                         | key_len | ref                                    | rows | Extra       |
+----+--------------------+--------------+-------------+------------------------------------------------------------------------------+-----------------------------+---------+----------------------------------------+------+-------------+
|  1 | PRIMARY            | orderfacto2_ | range       | PRIMARY,ORDER_RECORD_ID                                                      | PRIMARY                     | 4       | NULL                                   |    2 | Using where |
|  1 | PRIMARY            | orderrecor1_ | eq_ref      | PRIMARY,ORDER_ID                                                             | PRIMARY                     | 4       | pointcard.orderfacto2_.ORDER_RECORD_ID |    1 |             |
|  1 | PRIMARY            | order0_      | eq_ref      | PRIMARY                                                                      | PRIMARY                     | 4       | pointcard.orderrecor1_.ORDER_ID        |    1 | Using index |
|  1 | PRIMARY            | relateorde3_ | ref         | ORDER_ID,relate_order_tab_relate_internet_order_id,relate_order_tab_order_id | relate_order_tab_order_id   | 4       | pointcard.orderrecor1_.ORDER_ID        |    1 | Using where |
|  2 | DEPENDENT SUBQUERY | order4_      | ref_or_null | PRIMARY,idx_internet_order_id,order_tab_internet_order_id                    | order_tab_internet_order_id | 5       | func                                   |    2 | Using where |
|  2 | DEPENDENT SUBQUERY | relateorde7_ | ref         | ORDER_ID,relate_order_tab_relate_internet_order_id,relate_order_tab_order_id | relate_order_tab_order_id   | 4       | pointcard.order4_.ID                   |    1 | Using where |
|  2 | DEPENDENT SUBQUERY | orderrecor5_ | ref         | PRIMARY,ORDER_ID                                                             | ORDER_ID                    | 4       | pointcard.order4_.ID                   |    1 |             |
|  2 | DEPENDENT SUBQUERY | orderfacto6_ | ref         | PRIMARY,ORDER_RECORD_ID                                                      | ORDER_RECORD_ID             | 4       | pointcard.orderrecor5_.ID              |    1 | Using where |
+----+--------------------+--------------+-------------+------------------------------------------------------------------------------+-----------------------------+---------+----------------------------------------+------+-------------+
很显然,这是一个用hibernate生成的sql语句,执行计划显示格式比较乱。
其中,在主查询和子查询中所涉及的4个表记录数如下:
mysql> select count(1) from  order_tab order0_;
+----------+
| count(1) |
+----------+
| 30737230 |
+----------+
1 row in set (0.00 sec)
mysql> select count(1) from  order_record_tab orderrecor1_;
+----------+
| count(1) |
+----------+
| 31298527 |
+----------+
1 row in set (0.00 sec)
mysql> select count(1) from  order_factory_tab orderfacto2_;
+----------+
| count(1) |
+----------+
|   155895 |
+----------+
1 row in set (0.00 sec)
mysql> select count(1) from  relate_order_tab relateorde3_;
+----------+
| count(1) |
+----------+
|     9397 |
+----------+
1 row in set (0.00 sec)
 
按照sql的执行计划,主查询中先对orderfacto2_做范围扫描(range),然后和 orderrecor1_、order0_分别做等值连接(eq_ref),最后再和 relateorde3_做ref连接,这个过程是没有问题的。而子查询和主查询是完全系统的,但是子查询显然和主查询的执行计划不同,这点令人疑惑。在 生产机上此语句的执行计划如下:
+----+--------------------+--------------+--------+------------------------------------------------------------------------------+---------------------------+---------+----------------------------------------+------+-------------+
| id | select_type        | table        | type   | possible_keys                                                                | key                       | key_len | ref                                    | rows | Extra       |
+----+--------------------+--------------+--------+------------------------------------------------------------------------------+---------------------------+---------+----------------------------------------+------+-------------+
|  1 | PRIMARY            | orderfacto2_ | range  | PRIMARY,ORDER_RECORD_ID                                                      | PRIMARY                   | 4       | NULL                                   |    2 | Using where |
|  1 | PRIMARY            | orderrecor1_ | eq_ref | PRIMARY,ORDER_ID                                                             | PRIMARY                   | 4       | pointcard.orderfacto2_.ORDER_RECORD_ID |    1 |             |
|  1 | PRIMARY            | order0_      | eq_ref | PRIMARY                                                                      | PRIMARY                   | 4       | pointcard.orderrecor1_.ORDER_ID        |    1 | Using index |
|  1 | PRIMARY            | relateorde3_ | ref    | ORDER_ID,relate_order_tab_relate_internet_order_id,relate_order_tab_order_id | relate_order_tab_order_id | 4       | pointcard.orderrecor1_.ORDER_ID        |    1 | Using where |
|  2 | DEPENDENT SUBQUERY | orderfacto6_ | range  | PRIMARY,ORDER_RECORD_ID                                                      | PRIMARY                   | 4       | NULL                                   |    2 | Using where |
|  2 | DEPENDENT SUBQUERY | orderrecor5_ | eq_ref | PRIMARY,ORDER_ID                                                             | PRIMARY                   | 4       | pointcard.orderfacto6_.ORDER_RECORD_ID |    1 |             |
|  2 | DEPENDENT SUBQUERY | order4_      | eq_ref | PRIMARY,idx_internet_order_id,order_tab_internet_order_id                    | PRIMARY                   | 4       | pointcard.orderrecor5_.ORDER_ID        |    1 | Using where |
|  2 | DEPENDENT SUBQUERY | relateorde7_ | ref    | ORDER_ID,relate_order_tab_relate_internet_order_id,relate_order_tab_order_id | relate_order_tab_order_id | 4       | pointcard.order4_.ID                   |    1 | Using where |
+----+--------------------+--------------+--------+------------------------------------------------------------------------------+---------------------------+---------+----------------------------------------+------+-------------+
8 rows in set (0.03 sec)
 
和我们意料的一样,子查询的执行计划和主查询完全相同,速度也很快。怀疑测试机上的表数据统计信息是否没有更新,但是由于analyze时间很长,此步骤暂时不能做,想到的解决办法如下:
1、使用straight join按照主查询的连接查询进行连接;
2、让开发人员修改应用,将查询分为两步,先做子查询,得到结果后再做主查询;
3、和2想法类似,不过使用临时表存储子查询结果,再做主查询。
显然,方法1是解决问题最快的方法,我们改写后的sql如下:
select order0_.id as x0_0_
from order_tab order0_,
order_record_tab orderrecor1_,
order_factory_tab orderfacto2_,
 relate_order_tab relateorde3_
where (order0_.id=orderrecor1_.order_id )
          and(orderrecor1_.id=orderfacto2_.order_record_id )
          and(relateorde3_.order_id=order0_.id )
          and(orderfacto2_.id in(174028 , 174029))
          and((relateorde3_.relate_internet_order_id is not null ))
          and(relateorde3_.relate_internet_order_id not in
(select order4_.internet_order_id
from
order_factory_tab orderfacto6_
STRAIGHT_JOIN
order_record_tab orderrecor5_
STRAIGHT_JOIN
order_tab order4_
STRAIGHT_JOIN
relate_order_tab relateorde7_
where (order4_.id=orderrecor5_.order_id )and(orderrecor5_.id=orderfacto6_.order_record_id )and(relateorde7_.order_id=order4_.id )and(orderfacto6_.id in(174028 , 174029))and((relateorde7_.relate_internet_order_id is not null ))));
执行计划变为:
+----+--------------------+--------------+--------+------------------------------------------------------------------------------+---------------------------+---------+----------------------------------------+------+-------------+
| id | select_type        | table        | type   | possible_keys                                                                | key                       | key_len | ref                                    | rows | Extra       |
+----+--------------------+--------------+--------+------------------------------------------------------------------------------+---------------------------+---------+----------------------------------------+------+-------------+
|  1 | PRIMARY            | orderfacto2_ | range  | PRIMARY,ORDER_RECORD_ID                                                      | PRIMARY                   | 4       | NULL                                   |    2 | Using where |
|  1 | PRIMARY            | orderrecor1_ | eq_ref | PRIMARY,ORDER_ID                                                             | PRIMARY                   | 4       | pointcard.orderfacto2_.ORDER_RECORD_ID |    1 |             |
|  1 | PRIMARY            | order0_      | eq_ref | PRIMARY                                                                      | PRIMARY                   | 4       | pointcard.orderrecor1_.ORDER_ID        |    1 | Using index |
|  1 | PRIMARY            | relateorde3_ | ref    | ORDER_ID,relate_order_tab_relate_internet_order_id,relate_order_tab_order_id | relate_order_tab_order_id | 4       | pointcard.orderrecor1_.ORDER_ID        |    1 | Using where |
|  2 | DEPENDENT SUBQUERY | orderfacto6_ | range  | PRIMARY,ORDER_RECORD_ID                                                      | PRIMARY                   | 4       | NULL                                   |    2 | Using where |
|  2 | DEPENDENT SUBQUERY | orderrecor5_ | eq_ref | PRIMARY,ORDER_ID                                                             | PRIMARY                   | 4       | pointcard.orderfacto6_.ORDER_RECORD_ID |    1 |             |
|  2 | DEPENDENT SUBQUERY | order4_      | eq_ref | PRIMARY,idx_internet_order_id,order_tab_internet_order_id                    | PRIMARY                   | 4       | pointcard.orderrecor5_.ORDER_ID        |    1 | Using where |
|  2 | DEPENDENT SUBQUERY | relateorde7_ | ref    | ORDER_ID,relate_order_tab_relate_internet_order_id,relate_order_tab_order_id | relate_order_tab_order_id | 4       | pointcard.order4_.ID                   |    1 | Using where |
+----+--------------------+--------------+--------+------------------------------------------------------------------------------+---------------------------+---------+----------------------------------------+------+-------------+
8 rows in set (0.00 sec)
速度也随之变得飞快。
不过,最后应用还是按方法2进行了修改,让晦涩的语句变动更加简单、易读。