由一条sql语句想到的子查询优化
摘要:相信大家都使用过子查询,因为使用子查询可以一次性的完成很多逻辑上需要多个步骤才能完成的SQL操作,比较灵活,我也喜欢用,可最近因为一条包含子查询的select count(*)语句导致点开管理系统的一个功能模块列表时,耗时44几秒,到了不可容忍的地步,定位发现是因为未加索引和用了子查询导致,不加索引导致查询慢好理解,但子查询也会引起查询效率过低吗?没错,所以本文就以这次案例来重新认识下MySQL子查询。
特别说明:本文介绍的是在MySQL5.5.6版本下子查询的案例,5.5.29版本的我也试过也会有子查询效率低的问题。另外有关本文用到的sql及数据都在附录部分,有需要的可自行下载测试!
一、问题定位过程
1.1 问题现象
点击系统中某个列表功能模块发现很慢,开启log日志发现使用到了如下的sql语句来统计符合要求的总记录数,以进行分页使用
select count(*) from (select
schedule_id, schedule_code ,resource_code, schedule_type, schedule.oper_id, schedule.oper_time,
start_date, end_date, start_time, end_time, img_id, video_id, display_time,
schedule_color, terrace_code, stb_types, district_codes, user_group_codes,
igroup_code, schedule_status, schedule_description, step_id, owner_id, aud.description, so.oper_name
from schedule_record as schedule
left join auditing_desc_record as aud
on schedule.schedule_code = aud.code
and aud.is_last_auditing = 1
left join system_oper as so
on owner_id = so.oper_id
where 1=1 and schedule_status = 7
order by schedule.schedule_code desc) myCount ;

1.2 explain分析
手动执行该sql发现竟然用了21.18秒,怀疑是未使用索引或者表数据量过大,于是用explain语句分析
explain
select count(*) from (select
schedule_id, schedule_code ,resource_code, schedule_type, schedule.oper_id, schedule.oper_time,
start_date, end_date, start_time, end_time, img_id, video_id, display_time,
schedule_color, terrace_code, stb_types, district_codes, user_group_codes,
igroup_code, schedule_status, schedule_description, step_id, owner_id, aud.description, so.oper_name
from schedule_record as schedule
left join auditing_desc_record as aud
on schedule.schedule_code = aud.code
and aud.is_last_auditing = 1
left join system_oper as so
on owner_id = so.oper_id
where 1=1 and schedule_status = 7
order by schedule.schedule_code desc) myCount ;

1.3 改写sql
当然,看到上图,我相信很容易看出来是没有加索引导致全表扫描(有3条type为ALL),查看索引发现确实如此,连接字段schedule.schedule_code和aud.code都没使用索引
show index from schedule_record;
show index from auditing_desc_record;


但是更成功引起我注意的是为什么明明用了明明用了子查询(内部查询)只扫描了1827和11265条,最后外部查询select count(*)却扫描了1827*11265=20581155条记录?怀疑是子查询的导致,于是决定改写sql,看看不用子查询的效果
select
count(schedule_code)
from schedule_record as schedule
left join auditing_desc_record as aud
on schedule.schedule_code = aud.code
and aud.is_last_auditing = 1
left join system_oper as so
on owner_id = so.oper_id
where 1=1 and schedule_status = 7
order by schedule.schedule_code desc;


那是因为没有添加索引才会有子查询效率低的问题吗,接下来添加索引再试下
1.4 添加索引
ALTER TABLE auditing_desc_record ADD INDEX index_code (code);
ALTER TABLE schedule_record ADD INDEX index_schedule_code (schedule_code);
再查询,发现发现不用子查询效率依然要比用了子查询效率高些

这样对比不难发现,在这种情况下,用子查询效率确实更低,因为这里每次子查询每次都需要建立临时表,它会把结果集都存到临时表,这样外部查询select count(*)又重新扫描一次临时表,导致用时更长,扫描效率更低
但仅由此得出子查询效率低似乎太过草莽了。为验证我的想法,于是网上搜集了一些资料来确认下。
二、更多关于子查询效率的问题
《高性能MySQL》,第4.4节“MySQL查询优化器的限制”4.4.1小节“关联子查询”正好讲到这个问题。
MySQL有时优化子查询很差,特别是在WHERE从句中的IN()子查询。像上面我碰到的情况,其实我的想法是MySQL会把
select * from abc_number_prop where number_id in (select number_id from abc_number_phone where phone = '82306839');
变成下面的样子
select * from abc_number_prop where number_id in (8585, 10720, 148644, 151307, 170691, 221897);
但不幸的是,实际情况正好相反。MySQL试图让它和外面的表产生联系来“帮助”优化查询,它认为下面的exists形式更有效率
select * from abc_number_prop where exists (select * from abc_number_phone where phone = '82306839' and number_id = abc_number_prop.number_id);
由此看,在这两种场合缺失不太适合使用子查询,当然文中说到:但是总是认为子查询效率很差也是不对的,有时候可能子查询更好些。怎么确定这个事情呢,应该经过评测来决定(执行查询、用desc/explain等来看)。
在网上也能找到《高性能MySQL》的这节内容
参考资料4:MySQL 数据库优化(12)Limitations of the MySQL Query Optimizer
三、附录
3.1 表结构
-- ----------------------------
-- Table structure for auditing_desc_record
-- ----------------------------
DROP TABLE IF EXISTS `auditing_desc_record`;
CREATE TABLE `auditing_desc_record` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`code` varchar(50) NOT NULL COMMENT '记录编号',
`module_flag` int(5) NOT NULL COMMENT '模块标识',
`oper_id` int(11) NOT NULL COMMENT '操作人',
`oper_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP COMMENT '操作时间',
`status` int(5) NOT NULL COMMENT '记录状态',
`description` varchar(250) NOT NULL COMMENT '审核说明',
`is_last_auditing` int(2) NOT NULL COMMENT '是否最后一次审核',
`auditing_count` int(5) NOT NULL COMMENT '记录审核流程次数',
`reaudit_description` varchar(250) DEFAULT NULL,
`is_last_reauditing` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=14518 DEFAULT CHARSET=utf8; -- ----------------------------
-- Table structure for schedule_record
-- ----------------------------
DROP TABLE IF EXISTS `schedule_record`;
CREATE TABLE `schedule_record` (
`schedule_id` int(11) NOT NULL AUTO_INCREMENT,
`schedule_code` varchar(30) NOT NULL,
`schedule_type` int(5) NOT NULL,
`resource_code` varchar(30) DEFAULT NULL,
`oper_id` int(11) DEFAULT NULL,
`oper_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`start_date` date NOT NULL,
`end_date` date NOT NULL,
`start_time` int(10) NOT NULL,
`end_time` int(10) NOT NULL,
`img_id` int(11) DEFAULT NULL,
`video_id` int(11) DEFAULT NULL,
`display_time` int(5) DEFAULT NULL,
`schedule_color` varchar(8) NOT NULL,
`terrace_code` varchar(30) DEFAULT NULL,
`stb_types` text,
`district_codes` text,
`user_group_codes` text,
`igroup_code` varchar(50) DEFAULT NULL,
`schedule_status` int(5) NOT NULL,
`schedule_description` varchar(200) DEFAULT NULL,
`step_id` int(11) DEFAULT NULL,
`owner_id` int(11) NOT NULL DEFAULT '',
PRIMARY KEY (`schedule_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2534 DEFAULT CHARSET=utf8; -- ----------------------------
-- Table structure for system_oper
-- ----------------------------
DROP TABLE IF EXISTS `system_oper`;
CREATE TABLE `system_oper` (
`oper_id` int(11) NOT NULL AUTO_INCREMENT,
`oper_name` varchar(20) DEFAULT NULL,
`oper_password` varchar(40) DEFAULT NULL,
`oper_nikename` varchar(20) DEFAULT NULL,
`oper_city` varchar(20) DEFAULT NULL,
`oper_status` varchar(20) DEFAULT NULL,
`last_login_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`remark` varchar(500) DEFAULT NULL,
`history_password` varchar(80) DEFAULT NULL,
PRIMARY KEY (`oper_id`)
) ENGINE=InnoDB AUTO_INCREMENT=102 DEFAULT CHARSET=utf8;
3.2 表数据
有需要的请下载这个压缩包解压导入即可
下载地址:https://files.cnblogs.com/files/zishengY/sub_query%3B.zip
学习本就是一个不断模仿、练习、再到最后面自己原创的过程。
虽然可能从来不能写出超越网上通类型同主题博文,但为什么还是要写?
于自己而言,博文主要是自己总结。假设自己有观众,毕竟讲是最好的学(见下图)。于读者而言,笔者能在这个过程get到知识点,那就是双赢了。
当然由于笔者能力有限,或许文中存在描述不正确,欢迎指正、补充!
感谢您的阅读。如果本文对您有用,那么请点赞鼓励。
由一条sql语句想到的子查询优化的更多相关文章
- JavaWeb 学习007-4个页面,5条sql语句(添加、查看、修改、删除)2016-12-2
需要复习的知识: 关联查询 =================================================================================班级模块学 ...
- MyBatis7:MyBatis插件及示例----打印每条SQL语句及其执行时间
Plugins 摘一段来自MyBatis官方文档的文字. MyBatis允许你在某一点拦截已映射语句执行的调用.默认情况下,MyBatis允许使用插件来拦截方法调用 Executor(update.q ...
- 腾讯面试:一条SQL语句执行得很慢的原因有哪些?---不看后悔系列
说实话,这个问题可以涉及到 MySQL 的很多核心知识,可以扯出一大堆,就像要考你计算机网络的知识时,问你"输入URL回车之后,究竟发生了什么"一样,看看你能说出多少了. 之前腾讯 ...
- 一条SQL语句执行得很慢的原因有哪些?
说实话,这个问题可以涉及到 MySQL 的很多核心知识,可以扯出一大堆,就像要考你计算机网络的知识时,问你“输入URL回车之后,究竟发生了什么”一样,看看你能说出多少了. 之前腾讯面试的实话,也问到这 ...
- 一条SQL语句执行得很慢的原因有哪些?(转)
一条 SQL 语句执行的很慢,那是每次执行都很慢呢?还是大多数情况下是正常的,偶尔出现很慢呢?所以我觉得,我们还得分以下两种情况来讨论. 1.大多数情况是正常的,只是偶尔会出现很慢的情况. 2.在数据 ...
- MyBatis插件及示例----打印每条SQL语句及其执行时间
Plugins 摘一段来自MyBatis官方文档的文字. MyBatis允许你在某一点拦截已映射语句执行的调用.默认情况下,MyBatis允许使用插件来拦截方法调用 Executor(update.q ...
- Oracle一条SQL语句时快时慢
今天碰到一个非常奇怪的问题问题,一条SQL语句在PL/SQL developer中很慢,需要9s,问题SQL: SELECT * FROM GG_function_location f WHERE f ...
- select * from user 这条 SQL 语句,背后藏着哪些不可告人的秘密?
作为一名 Java开发人员,写 SQL 语句是常有的事,但是你知道 SQL 语句背后的处理逻辑吗?比如下面这条 SQL 语句: select * from user where id=1 执行完这条语 ...
- 一条SQL语句执行得很慢的原因有哪些
说实话,这个问题可以涉及到 MySQL 的很多核心知识,可以扯出一大堆,就像要考你计算机网络的知识时,问你"输入URL回车之后,究竟发生了什么"一样,看看你能说出多少了. 之前腾讯 ...
随机推荐
- miniui 给表格行添加监听事件的几种方法以及点击某列列名数据不能排序的问题
最近在使用miniui框架做开发,在做表格行的点击监听事件中发现了几个属性,都可以起到监听效果但是执行的结果却大有不同.好了废话不多说,直接上代码. <div id="pageGrid ...
- jquery选择器项目实例分析
首先废话一句,jQuery选择器真心很强大! 在项目中遇到这么一个问题easyui的问题 如图所示,当前页面显示的是"原始报文查询"的页面,当时左侧导航栏却选中的是"重 ...
- SpringBoot集成jsp
一.springBoot集成jsp: 1.修改pom文件 <!--集成jsp所需jar包--> <!--jsp页面使用jstl标签--> <dependency> ...
- SQL性能分析之执行计划
一直想找一些关于SQL语句性能调试的权威参考,但是有参考未必就能够做好调试的工作.我深信实践中得到的经验是最珍贵的,书本知识只是一个引导.本篇来源于<Inside Microsoft SQL S ...
- JAVA 语法2
1.算术运算符 运算符 运算规则 范例 结果 + 正号 +3 3 + 加 2+3 5 + 连接字符串 "中"+"国" "中国" - 负号 i ...
- Docker快速入门(二)
上篇文章<Docker快速入门(一)>介绍了docker的基本概念和image的相关操作,本篇将进一步介绍image,容器和Dockerfile. 1 image文件 (1)Docker ...
- java中面试可能会问的问题
为了明年的面试,把面试中可能遇到的关于java的问题记录在下面,纯个人理解,如果有误,请指正! 1.java中拷贝的三种方式,以及他们的区别. 这三种方式分别是:直接赋值,浅拷贝,深拷贝.第一种直接赋 ...
- logistic 回归
logistic回归 1.算法思想 根据给定的数据集确定分类的边界.这个分类的边界就是我们所要求的回归函数. 所谓的回归其实就是最佳拟合,回归函数就是确定最佳回归参数,然后对不同的特征赋予不同的权重 ...
- 在Java中谈尾递归--尾递归和垃圾回收的比较(转载)
我不是故意在JAVA中谈尾递归的,因为在JAVA中谈尾递归真的是要绕好几个弯,只是我确实只有JAVA学得比较好,虽然确实C是在学校学过还考了90+,真学得没自学的JAVA好 不过也是因为要绕几个弯,所 ...
- 国内各大支付平台的API地址
1丶目前国内最火的支付平台--蚂蚁金服开放平台(支付宝) https://open.alipay.com/platform/home.htm 2丶国内游戏帝国--腾讯(微信支付) https://pa ...
