浅谈MySQL中的子查询优化技巧

 更新时间:2015年05月07日 17:24:05   作者:罗龙九  
这篇文章主要介绍了浅谈MySQL中的子查询优化技巧,子查询的优化是MySQL诸多优化方法中的基本,需要的朋友可以参考下

mysql的子查询的优化一直不是很友好,一直有受业界批评比较多,也是我在sql优化中遇到过最多的问题之一,你可以点击这里 ,这里来获得一些信息,mysql在处理子查询的时候,会将子查询改写,通常情况下,我们希望由内到外,也就是先完成子查询的结果,然后在用子查询来驱动外查询的表,完成查询,但是恰恰相反,子查询不会先被执行;今天希望通过介绍一些实际的案例来加深对mysql子查询的理解:

案例:用户反馈数据库响应较慢,许多业务动更新被卡住;登录到数据库中观察,发现长时间执行的sql;

| 10437 | usr0321t9m9 | 10.242.232.50:51201 | oms | Execute | 1179 | Sending

Sql为:

select tradedto0_.* from a1 tradedto0_ where tradedto0_.tradestatus='1'
and (tradedto0_.tradeoid in (select orderdto1_.tradeoid from a2 orderdto1_ where
orderdto1_.proname like '%??%' or orderdto1_.procode like '%??%')) and tradedto0_.undefine4='1'
and tradedto0_.invoicetype='1' and tradedto0_.tradestep='0' and (tradedto0_.orderCompany like '0002%') order by tradedto0_.tradesign ASC, tradedto0_.makertime desc limit 15;

2.其他表的更新被阻塞:

update a1 set tradesign='DAB67634-795C-4EAC-B4A0-78F0D531D62F',
markColor=' #CD5555', memotime='2012-09- 22', markPerson='??' where tradeoid in ('gy2012092204495100032') ;

为了尽快恢复应用,将其长时间执行的sql kill掉后,应用恢复正常;
3.分析执行计划:

db@3306 :explain select tradedto0_.* from a1 tradedto0_ where tradedto0_.tradestatus='1' and (tradedto0_.tradeoid in (select orderdto1_.tradeoid
from a2 orderdto1_ where orderdto1_.proname like '%??%' or orderdto1_.procode like '%??%')) and tradedto0_.undefine4='1' and tradedto0_.invoicetype='1' and tradedto0_.tradestep='0' and (tradedto0_.orderCompany like '0002%') order by tradedto0_.tradesign ASC, tradedto0_.makertime desc limit 15;
+----+--------------------+------------+------+---------------+------+---------+------+-------+-----
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------------+------------+------+---------------+------+---------+------+-------+-----
| 1 | PRIMARY | tradedto0_ | ALL | NULL | NULL | NULL | NULL | 27454 | Using where; Using filesort |
| 2 | DEPENDENT SUBQUERY | orderdto1_ | ALL | NULL | NULL | NULL | NULL | 40998 | Using where |
+----+--------------------+------------+------+---------------+------+---------+------+-------+-----

从执行计划上,我们开始一步一步地进行优化:
首先,我们看看执行计划的第二行,也就是子查询的那部分,orderdto1_进行了全表的扫描,我们看看能不能添加适当的索引:
A.使用覆盖索引:

db@3306:alter table a2 add index ind_a2(proname,procode,tradeoid);
ERROR 1071 (42000): Specified key was too long; max key length is 1000 bytes

添加组合索引超过了最大key length限制:
B.查看该表的字段定义:

 db@3306 :DESC a2 ;
+---------------------+---------------+------+-----+---------+-------+
| FIELD        | TYPE     | NULL | KEY | DEFAULT | Extra |
+---------------------+---------------+------+-----+---------+-------+
| OID         | VARCHAR(50)  | NO  | PRI | NULL  |    |
| TRADEOID      | VARCHAR(50)  | YES |   | NULL  |    |
| PROCODE       | VARCHAR(50)  | YES |   | NULL  |    |
| PRONAME       | VARCHAR(1000) | YES |   | NULL  |    |
| SPCTNCODE      | VARCHAR(200) | YES |   | NULL  |    |

C.查看表字段的平均长度:

db@3306 :SELECT MAX(LENGTH(PRONAME)),avg(LENGTH(PRONAME)) FROM a2;
+----------------------+----------------------+
| MAX(LENGTH(PRONAME)) | avg(LENGTH(PRONAME)) |
+----------------------+----------------------+
|  95       |    24.5588 |

D.缩小字段长度

ALTER TABLE MODIFY COLUMN PRONAME VARCHAR(156);

再进行执行计划分析:

db@3306 :explain select tradedto0_.* from a1 tradedto0_ where tradedto0_.tradestatus='1' and (tradedto0_.tradeoid in (select orderdto1_.tradeoid from a2 orderdto1_ where orderdto1_.proname like '%??%' or orderdto1_.procode like '%??%')) and tradedto0_.undefine4='1' and tradedto0_.invoicetype='1' and tradedto0_.tradestep='0' and (tradedto0_.orderCompany like '0002%') order by tradedto0_.tradesign ASC, tradedto0_.makertime desc limit 15;
+----+--------------------+------------+-------+-----------------+----------------------+---------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------------+------------+-------+-----------------+----------------------+---------+
| 1 | PRIMARY | tradedto0_ | ref | ind_tradestatus | ind_tradestatus | 345 | const,const,const,const | 8962 | Using where; Using filesort |
| 2 | DEPENDENT SUBQUERY | orderdto1_ | index | NULL | ind_a2 | 777 | NULL | 41005 | Using where; Using index |
+----+--------------------+------------+-------+-----------------+----------------------+---------+

发现性能还是上不去,关键在两个表扫描的行数并没有减小(8962*41005),上面添加的索引没有太大的效果,现在查看t表的执行结果:

db@3306 :select orderdto1_.tradeoid from t orderdto1_ where orderdto1_.proname like '%??%' or orderdto1_.procode like '%??%';
Empty set (0.05 sec)

结果集为空,所以需要将t表的结果集做作为驱动表;
4.通过上面测试验证,普通的mysql子查询写法性能上是很差的,为mysql的子查询天然的弱点,需要将sql进行改写为关联的写法:

select tradedto0_.* from a1 tradedto0_ ,(select orderdto1_.tradeoid from a2 orderdto1_ where orderdto1_.proname like '%??%' or orderdto1_.procode like '%??%')t2 where tradedto0_.tradestatus='1' and (tradedto0_.tradeoid=t2.tradeoid ) and tradedto0_.undefine4='1' and tradedto0_.invoicetype='1' and tradedto0_.tradestep='0' and (tradedto0_.orderCompany like '0002%') order by tradedto0_.tradesign ASC, tradedto0_.makertime desc limit 15;

5.查看执行计划:

db@3306 :explain select tradedto0_.* from a1 tradedto0_ ,(select orderdto1_.tradeoid from a2 orderdto1_ where orderdto1_.proname like '%??%' or orderdto1_.procode like '%??%')t2 where tradedto0_.tradestatus='1' and (tradedto0_.tradeoid=t2.tradeoid ) and tradedto0_.undefine4='1' and tradedto0_.invoicetype='1' and tradedto0_.tradestep='0' and (tradedto0_.orderCompany like '0002%') order by tradedto0_.tradesign ASC, tradedto0_.makertime desc limit 15;
+----+-------------+------------+-------+---------------+----------------------+---------+------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+-------+---------------+----------------------+---------+------+
| 1 | PRIMARY | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Impossible WHERE noticed after reading const tables |
| 2 | DERIVED | orderdto1_ | index | NULL | ind_a2 | 777 | NULL | 41005 | Using where; Using index |
+----+-------------+------------+-------+---------------+----------------------+---------+------+

6.执行时间:

db@3306 :select tradedto0_.* from a1 tradedto0_ ,(select orderdto1_.tradeoid from a2 orderdto1_ where orderdto1_.proname like '%??%' or orderdto1_.procode like '%??%')t2 where tradedto0_.tradestatus='1' and (tradedto0_.tradeoid=t2.tradeoid ) and tradedto0_.undefine4='1' and tradedto0_.invoicetype='1' and tradedto0_.tradestep='0' and (tradedto0_.orderCompany like '0002%') order by tradedto0_.tradesign ASC, tradedto0_.makertime desc limit 15;
Empty set (0.03 sec)

缩短到了毫秒;

相关文章

  • MySQL5.7免安装版配置图文教程

    MySQL5.7免安装版配置图文教程

    Mysql是一个比较流行且很好用的一款数据库软件,如下记录了我学习总结的mysql免安装版的配置经验,感兴趣的的朋友参考下吧
    2017-09-09
  • 验证mysql是否安装成功的方法

    验证mysql是否安装成功的方法

    在本篇文章里小编给大家分享的是关于验证mysql是否安装成功的方法,需要的朋友们可以学习下。
    2020-06-06
  • mysql 8.0.18各版本安装及安装中出现的问题(精华总结)

    mysql 8.0.18各版本安装及安装中出现的问题(精华总结)

    这篇文章主要介绍了mysql 8.0.18各版本安装及安装中出现的问题,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-12-12
  • MySql按时,天,周,月进行数据统计

    MySql按时,天,周,月进行数据统计

    这篇文章主要介绍了MySql按时,天,周,月进行数据统计,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-08-08
  • MySQL常用命令与内部组件及SQL优化详情

    MySQL常用命令与内部组件及SQL优化详情

    这篇文章主要介绍了MySQL常用命令与内部组件及SQL优化详情,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的朋友可以参考一下
    2022-07-07
  • MySQL数据库表的增删改查操作(进阶)

    MySQL数据库表的增删改查操作(进阶)

    这篇文章主要给大家介绍了关于MySQL数据库表的增删改查操作,文章记录常用的SQL查询语句,包括数据库、表和数据的增删改查,逐渐扩展到其他比较复杂的查询语句,需要的朋友可以参考下
    2023-11-11
  • mysql使用xtrbackup+relaylog增量恢复注意事项

    mysql使用xtrbackup+relaylog增量恢复注意事项

    这篇文章主要介绍了mysql使用xtrbackup+relaylog增量恢复,本次实验mysql5.7.19.使用了GTID,row格式的binlog,本文结合实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2023-05-05
  • MySQL数据库备份与恢复方法

    MySQL数据库备份与恢复方法

    网站数据对我们对站长来说都是最宝贵的,我们平时应该养成良好的备份数据的习惯。
    2010-12-12
  • MySQL8.0新特性之支持原子DDL语句

    MySQL8.0新特性之支持原子DDL语句

    这MySQL 8.0开始支持原子数据定义语言(DDL)语句。此功能称为原子DDL。这篇文章主要介绍了MySQL8.0新特性——支持原子DDL语句,需要的朋友可以参考下
    2018-07-07
  • CentOS系统中MySQL5.1升级至5.5.36

    CentOS系统中MySQL5.1升级至5.5.36

    有相关测试数据说明从5.1到5.5+,MySQL性能会有明显的提升,具体的需要自己建立测试环境去实践下,今天我们就来操作下,并记录下来升级的具体步骤
    2017-07-07

最新评论