当前位置: 首页 > 编程日记 > 正文

浅谈 MySQL 子查询及其优化

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

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

SELECT i_id,sum(i_sell) AS i_sell
FROM table_data
WHERE i_id IN(SELECT i_idFROM table_dataWHERE 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_idFROM table_dataWHERE 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的子查询的优化一直不是很友好,一直有受业界批评比较多,也是我在sql优化中遇到过最多的问题之一,mysql在处理子查询的时候,会将子查询改写,通常情况下,我们希望由内到外,也就是先完成子查询的结果,然后在用子查询来驱动外查询的表,完成查询,但是恰恰相反,子查询不会先被执行;今天希望通过介绍一些实际的案例来加深对mysql子查询的理解。下面将介绍一个完整的案例及其分析、调优的过程与思路。

1、案例

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

| 10437 | usr0321t9m9 | 10.242.232.50:51201 | oms | Execute | 1179 | SendingSql为:SELECT tradedto0_.*
FROM a1 tradedto0_
WHERE tradedto0_.tradestatus='1'AND (tradedto0_.tradeoid IN(SELECT orderdto1_.tradeoidFROM 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_.tradeoidFROM 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_.tradeoidFROM 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_.tradeoidFROM 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_.tradeoidFROM 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_.tradeoidFROM 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)
缩短到了毫秒;

7、总结:

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

8、Refer:

[1] 生产库中遇到mysql的子查询  http://hidba.org/?p=412

[2] 浅谈mysql的子查询  http://hidba.org/?p=624

[3] mysql子查询的弱点  http://hidba.org/?p=260

转载于:https://my.oschina.net/leejun2005/blog/288533

相关文章:

[PHP] JQuery+Layer实现添加删除自定义标签代码

JQueryLayer实现添加删除自定义标签代码 实现效果如下&#xff1a; 实现代码如下&#xff1a; <!doctype html> <html> <head> <meta charset"utf-8"> <title>实用的文章自定义标签</title> <link rel"stylesheet"…

NB-IOT: Anchor Carrier 锚点载波

Anchor Carrier定义&#xff1a; Anchor carrier:in NB-IoT, a carrier where the UE assumes that NPSS/NSSS/NPBCH/SIB-NB are transmitted. Anchor carrier用以发送NPSS/NSSS/NPBCH/SIB-NB&#xff0c; 另外寻呼消息和随机接入过程也只能在AnchorCarrier上进行。 在使用I…

mysql8 my 010457_分享一下我在mysql5.6+mysql8数据库安装过程中的一些坑!

Mysql5.6安装下载好安装包后&#xff0c;在bin目录下用cmd打开&#xff0c;输入mysqld install 【服务名】新建个服务在windowsr输入services.msc即可查看服务怎样使用mysql在本地电脑上安装好mysql服务器后&#xff0c;使用命令开启mysql服务&#xff0c;命令为net start mysq…

14年12月CCF真题1-门禁系统

问题描述 涛涛最近要负责图书馆的管理工作,需要记录下每天读者的到访情况。每 位读者有一个编号,每条记录用读者的编号来表示。给出读者的来访记录,请 问每一条记录中的读者是第几次出现。 输入格式 输入的第一行包含一个整数n,表示涛涛的记录条数。 第二行包含n个整数,依次表…

[Oracle] - 性能优化工具(5) - AWRSQL

在AWR中定位到问题SQL语句后想要了解该SQL statement的详细运行计划&#xff0c;于是就用AWR报告中得到的SQL ID去V$SQL等几个动态性能视图中查询&#xff0c;但发现V$SQL或V$SQL_PLAN视图都已经找不到相应SQL ID的记录&#xff0c;一般来说这些语句已经从shared pool共享池中被…

三种基本排序的实现及其效率对比:冒泡排序、选择排序和插入排序

1 public class ThreeTypesOfBaseSort {2 // 三种基本排序的效率对比 3 public static void main(String[] args) {4 ThreeTypesOfBaseSort sort new ThreeTypesOfBaseSort();5 6 // 测试百万级别的数组排序&#xff0c;看三种基本排序的的效率差…

NB-IOT UE的小区接入过程

NB-IOT UE的小区接入过程如下&#xff1a;NPSS/NSSS/NPBCH的时频资源&#xff0c;可以参考&#xff1a;点击打开链接 下面详细介绍一下MIB-NB/SIB1-NB的获取过程。 MIB-NB传输 在sharetechnote中有详细的描述&#xff0c;如下&#xff1a; MIB-NB分成8个等长的可以独立编码的子…

android用户界面之菜单(Menu)教程实例汇总

一、Menu的基本介绍1.从头学Android之Menu选项菜单 http://www.apkbus.com/android-13930-1-1.html 2.Android 界面之Menu菜单的特性 http://www.apkbus.com/android-664-1-1.html 3.Android XML中自定义菜单 http://www.apkbus.com/android-50884-1-1.html 4.Android 基础菜单…

nosql mysql mongodb_关于NoSQL之MongoDB的一些总结

NoSQL已经流行了很长一段时间&#xff0c;那么究竟是什么场景下你才更需要用到这些“新兴事物”&#xff0c;就比如MongoDB&#xff1f;下面是一些总结&#xff1a;你期望一个更高的写负载默认情况下&#xff0c;对比事务安全&#xff0c;MongoDB更关注高的插入速度。如果你需要…

下载文件乱码问题

1.下载文件乱码问题 new String("免责声明.pdf".getBytes("utf-8"), "ISO-8859-1")&#xff1b; 2.图片转blog String path request.getSession().getServletContext().getRealPath("/"); String a picturename2…

MySQL全面优化,速度飞起来

在进行MySQL的优化之前&#xff0c;必须要了解的就是MySQL的查询过程&#xff0c;很多查询优化工作实际上就是遵循一些原则&#xff0c;让MySQL的优化器能够按照预想的合理方式运行而已。 图-MySQL查询过程 一、优化的哲学 注&#xff1a;优化有风险&#xff0c;涉足需谨慎 1、…

LTE - PRACH 时频资源介绍

PRACH&#xff1a; Physical Random Access Channel. PRACH用于传输random access preamble RA-preamble Format 一共包含4种格式&#xff0c;其中format0-3 用于frametype 1(FDD), format 0-4 用于frametype 2(TDD). spec: 36.211- table5.7.1-1另外参考sharetechnote&#xf…

对面向对象基本原则的总结

&#xff08;一&#xff09;代理模式 应用场景&#xff1a;当一个类的某些功能需要由别的类来实现&#xff0c;但是又不确定具体会是哪个类实现。 优势&#xff1a;解耦合 敏捷原则&#xff1a;开放-封闭原则 实例&#xff1a;tableview的 数据源delegate&#xff0c;通过和pro…

python turtle画画 30排以内_Python竟能画这么漂亮的花,帅呆了(代码分享)

阅读本文大概需要3分钟关于函数和模块讲了这么久&#xff0c;我一直想用一个好玩有趣的小例子来总结一下&#xff0c;同时也作为实战练习一下。趣味编程其实是最好的学习途径&#xff0c;回想十几年前我刚毕业的时候&#xff0c;第一份工作就给手机上写app&#xff0c;当时觉得…

关于Windows 2003下开启防火墙后不能通过FTP问题解决

在Windows server 2003上做了个基于IIS的FTP服务。但是不久就发现一个问题&#xff0c;当系统开启防火墙后在其它机子上不能登录FTP服务器&#xff0c;但是又不想把Windows的防火墙晾起来&#xff0c;所以就尝试下突破这个限制。当时做了两步处理&#xff1a;&#xff08;1&…

【多线程】ConcurrentLinkedQueue 的实现原理

1. 引言 在并发编程中我们有时候需要使用线程安全的队列。如果我们要实现一个线程安全的队列有两种实现方式&#xff1a;一种是使用阻塞算法&#xff0c;另一种是使用非阻塞算法。使用阻塞算法的队列可以用一个锁&#xff08;入队和出队用同一把锁&#xff09;或两个锁&#xf…

shell 脚本简单入门

好久不写shell脚本&#xff0c;有些生疏。总结下shell的语法&#xff0c;以便后续参考&#xff0c;快速捡起来。 shell 脚本执行的3种方式&#xff1a; 1). ./xx.sh &#xff08;xx.sh 需要有执行权限&#xff09; 2). source xx.sh 3). bash xx.sh 变量定义 var2 //注意&…

ubuntu 在线安装mysql_Ubuntu下安装MySQL5.6

我想我们不应该在安装软件上面耽误太多时间&#xff0c;但是很多时候&#xff0c;我们去被安装挡在了门外&#xff0c;尤其是初次在Linux下。作为一个程序猿&#xff0c;最近决定转战linux&#xff0c;MySQL是必须要有的&#xff0c;讲一下我的安装过程。在Ubuntu下安装MySQL有…

js循环动态绑定带参数函数遇到的问题及解决方案[转]

今天写原生javascript时&#xff0c;想利用绑定事件实现类似jquery中on方法的功能&#xff1a;于是有了for循环里绑定事件&#xff0c;无意中发现定义类能解决好多问题&#xff01; 例如&#xff1a;一个不确定长度的列表&#xff0c;在鼠标经过某一条的时候改变背景 1 2 <…

基于Picture Library创建的图片文档库中的上传多个文件功能(upload multiple files)报错怎么解决?...

复现过程 首先&#xff0c;我创建了一个基于Picture Library的图片文档库&#xff0c;名字是 Pic Lib 创建完毕后&#xff0c;我点击它的Upload 下拉菜单&#xff0c;点击Upload Picture按钮 在弹出的对话框中点击 Upload Multiple Files按钮 结果返回了下面的错误页面 如果查看…

vi 环境,跳转函数定义

1, 安装 sudo apt-get install exuberant-ctags 2. 生成tags ctags -R . 3. 跳转 将光标移到想要跳转的函数或变量 快捷键 " CTRL ] " 4. 回转 回到跳转之前的位置&#xff0c; 只需要通过快捷键“ CTRL T ” 其它更详细&#xff1a; https://www.cnblogs.com/ca…

linux kernel内存回收机制

http://www.wowotech.net/linux_kenrel/233.html无论计算机上有多少内存都是不够的&#xff0c;因而linux kernel需要回收一些很少使用的内存页面来保证系统持续有内存使用。页面回收的方式有页回写、页交换和页丢弃三种方式&#xff1a;如果一个很少使用的页的后备存储器是一个…

Python 学习笔记01

print&#xff1a;直接输出 type&#xff0c;求类型 数据类型&#xff1a;字符串&#xff0c;整型&#xff0c;浮点型&#xff0c;Bool型 note01.py # python learning note 01 print(Hello world!) a 10 print a print type(a) a 1.3 print a,type(a) print a Tr…

vuecli 编译后部署_基于vue-cli 打包时抽离项目相关配置文件详解

前言&#xff1a;当使用vue-cli进行开发时时常需要动态配置一些设置&#xff0c;比如接口的请求地址(axios.defaults.baseURL)&#xff0c;这些设置可能需要在项目编译后再进行设置的&#xff0c;所以在vue-cli里我们需要对这些配置文件进行抽离&#xff0c;不让webpack把配置文…

intel xdk 打ios的ipa包

1、打包 2、点击edit。下载csr文件&#xff0c;然后上传到苹果开发者网址&#xff0c;生成cer文件 上面两步搞完&#xff0c;把最后的按钮设置成"yes" 3、上传配置文件 转载于:https://www.cnblogs.com/linn/p/3844930.html

《C++程序设计POJ》《WEEK7 输入输出和模板》《流操纵算子》《文件读写》《二进制文件读写》...

函数指针&#xff0c;运算符重载 人懂我精&#xff0c;人精我深 用的时候查一查手册 dat 二进制文件 如果不指定文件夹&#xff0c;就是生成在当前文件夹&#xff0c;什么是当前文件夹&#xff1f;可执行文件所在的文件夹 绝对路径 相对路径 文件的读写指针 ifstream ofsteam s…

linux内存管理 之 内存节点和内存分区(Zone)

https://www.cnblogs.com/youngerchina/p/5624516.html Linux支持多种硬件体系结构&#xff0c;因此Linux必须采用通用的方法来描述内存&#xff0c;以方便对内存进行管理。为此&#xff0c;Linux有了内存节点、内存区、页框的概念&#xff0c;这些概念也是一目了然的。 内存节…

BZOJ 3585: mex( 离线 + 线段树 )

离线, 询问排序.先处理出1~i的答案, 这样可以回答左端点为1的询问.完成后就用seq(1)将1到它下一次出现的位置前更新. 不断这样转移就OK了--------------------------------------------------------------------#include<bits/stdc.h>using namespace std;#define M(l, r…

yum安装mysql后密码_Centos7:yum安装MySQL5.7后如何设置root密码

Centos下安装软件的方式很简单&#xff0c;只需要通过yum install xxx命令即可。第一步当然检查是否有mysql的yum源&#xff0c;命令&#xff1a;yumlist|grep mysql-community[主要还是安装开源的社区版]如果没有如图所示的和mysql*相关的数据源&#xff0c;可去官网上下载相关…

iOS开发:使用Block在两个界面之间传值(Block高级用法:Block传值)

使用Block的地方很多&#xff0c;其中传值只是其中的一小部分&#xff0c;下面介绍Block在两个界面之间的传值&#xff1a;先说一下思想&#xff1a;首先&#xff0c;创建两个视图控制器&#xff0c;在第一个视图控制器中创建一个UILabel和一个UIButton&#xff0c;其中UILabel…