浅谈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) 缩短到了毫秒;

总结: 1. mysql子查询在执行计划上有着明显的弱点,需要将子查询进行改写 可以参考: a. 生产库中遇到mysql的子查询:http://hidba.org/?p=412 b. 内建的builtin InnoDB,子查询阻塞更新:http://hidba.org/?p=456 2. 在表结构设计上,不要随便使用varchar(N)的大字段,导致无法使用索引 可以参考: a. JDBC内存管理—varchar2(4000)的影响:http://hidba.org/?p=31 b. innodb中大字段的限制:http://hidba.org/?p=144 c. innodb使用大字段text,blob的一些优化建议:http://hidba.org/?p=551

使用过oracle或者其他关系数据库的DBA或者开发人员都有这样的经验,在子查询上都认为数据库已经做过优化,能够很好的选择驱动表执行,然后在把该经验移植到mysql数据库上,但是不幸的是,mysql在子查询的处理上有可能会让你大失所望,在我们的生产系统上就由于碰到了这个问题:

select  i_id, sum(i_sell) as i_sell

from table_data

where i_id in (select i_id from table_data where Gmt_create >= ’2011-10-07 00:00:00′)

group by i_id;

(备注:sql的业务逻辑可以打个比方:先查询出10-07号新卖出的100本书,然后在查询这新卖出的100本书在全年的销量情况)。

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

针对上面的查询,由于table_data这张表的数据有70W的数据,同时子查询中的数据较多,有大量是重复的,这样就需要关联近70W次,大量的关联导致这条sql执行了几个小时也没有执行完成,所以我们需要改写sql:

SELECT t2.i_id, SUM(t2.i_sell) AS sold

FROM (SELECT distinct i_id FROM table_data

WHERE gmt_create >= ’2011-10-07 00:00:00′) t1,  table_data t2

WHERE t1.i_id = t2.i_id GROUP BY t2.i_id;

我们将子查询改为了关联,同时在子查询中加上distinct,减少t1关联t2的次数;

改造后,sql的执行时间降到100ms以内。

MySQL子查询的优缺点_浅谈mysql的子查询相关推荐

  1. mysql自定义函数的优缺点_浅谈MySQL创建自定义函数漏洞的利用和防止

    前一阵子网上风靡的MySQL的udf.dll提权我有所了解-近日由于不再在IDC行业工作了-所以也有所淡忘- 只是最近实在手痒,就决定对我的站点所在的服务器下手--.正好用上这招了- 站点的服务器是W ...

  2. mysql事务的管理方式_浅谈MySQL事务管理(基础)

    本篇文章给大家带来的内容是浅谈MySQL事务管理(基础),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助.事务处理用来维护数据库等完整性,保证mysql操作要么成功,要么失败(myisa ...

  3. mysql存储过程set什么意思_浅谈MySQL存储过程中declare和set定义变量的区别

    在存储过程中常看到declare定义的变量和@set定义的变量.简单的来说,declare定义的类似是局部变量,@set定义的类似全局变量. 1.declare定义的变量类似java类中的局部变量,仅 ...

  4. mysql锁的应用场景_浅谈Mysql共享锁、排他锁、悲观锁、乐观锁及其使用场景

    Mysql共享锁.排他锁.悲观锁.乐观锁及其使用场景 一.相关名词 |--表级锁(锁定整个表) |--页级锁(锁定一页) |--行级锁(锁定一行) |--共享锁(S锁,MyISAM 叫做读锁) |-- ...

  5. mysql笛卡尔积查询很慢_浅谈MySQL使用笛卡尔积原理进行多表查询

    我就废话不多说了,大家还是直接看代码吧~create or replace function aa1(a1 integer[],a2 bigint) returns void AS $$declare ...

  6. mysql inner和left优化_浅谈mysql中的left join和inner join性能及优化策略

    前言 看一下 下面的sql语句:select * from a left join b on a.x = b.x left join c on c.y = b.y 这样的多个left join组合的时 ...

  7. mysql 用户通配符_浅谈mysql通配符进行模糊查询的实现方法

    在mysql数据库中,当我们需要模糊查询的时候 ,我们会使用到通配符. 首先我们来了解一下2个概念,一个是操作符,一个是通配符. 操作符 like就是SQL语句中的操作符,它的作用是指示在SQL语句后 ...

  8. 支付宝的数据库是MySQL变种_浅谈MySql的储存引擎(表类型)

    浅谈mysql的存储引擎(表类型) 什么是MySql数据库 通常意义上,数据库也就是数据的集合,具体到计算机上数据库可以是存储器上一些文件的集合或者一些内存数据的集合. 我们通常说的MySql数据库, ...

  9. mysql revoke 授权_浅谈MySQL中授权(grant)和撤销授权(revoke)用法详解

    MySQL 赋予用户权限命令的简单格式可概括为: grant 权限 on 数据库对象 to 用户 一.grant 普通数据用户,查询.插入.更新.删除 数据库中所有表数据的权利 grant selec ...

最新文章

  1. SharePoint 如何找到List的Template ID
  2. linux sort 时间排序,linux sort多字段排序实例解析
  3. EF Core中关于System.Linq.Dynamic.Core的使用(转载)
  4. WAMPServer自定义网站根目录
  5. WebApi用户登录验证及服务器端用户状态存取
  6. 黑马程序员之《String 类练习》
  7. Php和Mysql乱码问题
  8. QT的QQuickImageProvider类的使用
  9. JDK 10:从Java访问Java应用程序的进程ID
  10. 程序员求助:被领导强行要求写Bug该怎么办?网友的回答让我笑翻
  11. oracle查看角色内容,oracle创建,查看用户和角色,用户角色赋权
  12. Flutter 雷达扫描效果、Flutter旋转扫描
  13. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之LED驱动框架--面向对象、分层设计思想
  14. 操作class属性的新API--classList
  15. android shape使用总结
  16. Red5边源服务器集群部署
  17. restfulframework详解
  18. JAVA三大框架入门
  19. 释放数据价值的真正法宝,数据要素市场化开发迫在眉睫
  20. 华三交换机开机dhcp snooping

热门文章

  1. 解决电脑网络图标消失,电脑连接不上网络的问题总结。
  2. 2023西安交通大学计算机考研信息汇总
  3. [转]开源大数据处理工具汇总
  4. C#文件选择和另存为
  5. pta 计算机通信(并查集)
  6. 浅谈四种常见的agv导航方式及各自的优缺点
  7. 案例:自动登录12306
  8. 跨境电商开发制作搭建
  9. CodeQL笔记之基本语法(一)
  10. 原生js实现购物车添加删除商品、计算价格功能