SQL执行效率提升几万倍的操作详解!
作者:风过无痕-唐
场景
我用的数据库是MySQL5.6,下面简单的介绍下场景
课程表:
create table Course(c_id int PRIMARY KEY,name varchar(10))
数据100条
学生表:
create table Student(id int PRIMARY KEY,name varchar(10))
数据70000条
学生成绩表SC
CREATE table SC(sc_id int PRIMARY KEY,s_id int,c_id int,score int)
数据70w条
查询目的:查找语文考100分的考生
查询语句:
select s.* from Student s where s.s_id in (select s_id from SC sc where sc.c_id = 0 and sc.score = 100 )
执行时间:30248.271s
晕,为什么这么慢,先来查看下查询计划:
EXPLAIN select s.* from Student s where s.s_id in (select s_id from SC sc where sc.c_id = 0 and sc.score = 100 )
发现没有用到索引,type全是ALL,那么首先想到的就是建立一个索引,建立索引的字段当然是在where条件的字段。
先给sc表的c_id和score建个索引
CREATE index sc_c_id_index on SC(c_id);
CREATE index sc_score_index on SC(score);
再次执行上述查询语句,时间为: 1.054s
快了3w多倍,大大缩短了查询时间,看来索引能极大程度的提高查询效率,建索引很有必要。
很多时候都忘记建索引了,数据量小的的时候压根没感觉,这优化的感觉挺爽。
但是1s的时间还是太长了,还能进行优化吗,仔细看执行计划:
查看优化后的sql:
SELECT`YSB`.`s`.`s_id` AS `s_id`,`YSB`.`s`.`name` AS `name`
FROM`YSB`.`Student` `s`
WHERE< in_optimizer > (`YSB`.`s`.`s_id` ,< EXISTS > (SELECTFROM`YSB`.`SC` `sc`WHERE((`YSB`.`sc`.`c_id` = 0)AND (`YSB`.`sc`.`score` = 100)AND (< CACHE > (`YSB`.`s`.`s_id`) = `YSB`.`sc`.`s_id`))))
补充:这里有朋友问怎么查看优化后的语句,方法如下:
在命令窗口执行
有type=all
按照我之前的想法,该sql的执行的顺序应该是先执行子查询
select s_id from SC sc where sc.c_id = 0 and sc.score = 100
耗时:0.001s
得到如下结果:
然后再执行
select s.* from Student s where s.s_id in(7,29,5000)
耗时:0.001s
这样就是相当快了啊,Mysql竟然不是先执行里层的查询,而是将sql优化成了exists子句,并出现了EPENDENT SUBQUERY,mysql是先执行外层查询,再执行里层的查询,这样就要循环70007*8次。
那么改用连接查询呢?
SELECT s.* from Student sINNER JOIN SC scon sc.s_id = s.s_idwhere sc.c_id=0 and sc.score=100
这里为了重新分析连接查询的情况,先暂时删除索引sc_c_id_index,sc_score_index
执行时间是:0.057s
效率有所提高,看看执行计划:
这里有连表的情况出现,我猜想是不是要给sc表的s_id建立个索引
CREATE index sc_s_id_index on SC(s_id);
show index from SC
在执行连接查询
时间: 1.076s,竟然时间还变长了,什么原因?查看执行计划:
优化后的查询语句为:
SELECT`YSB`.`s`.`s_id` AS `s_id`,`YSB`.`s`.`name` AS `name`
FROM`YSB`.`Student` `s`
JOIN `YSB`.`SC` `sc`
WHERE((`YSB`.`sc`.`s_id` = `YSB`.`s`.`s_id`)AND (`YSB`.`sc`.`score` = 100)AND (`YSB`.`sc`.`c_id` = 0))
貌似是先做的连接查询,再进行的where条件过滤
回到前面的执行计划:
这里是先做的where条件过滤,再做连表,执行计划还不是固定的,那么我们先看下标准的sql执行顺序:
正常情况下是先join再进行where过滤,但是我们这里的情况,如果先join,将会有70w条数据发送join做操,因此先执行where过滤是明智方案
现在为了排除mysql的查询优化,我自己写一条优化后的sql
SELECTs.*
FROM(SELECT*FROMSC scWHEREsc.c_id = 0AND sc.score = 100) t
INNER JOIN Student s ON t.s_id = s.s_id
即先执行sc表的过滤,再进行表连接,执行时间为:0.054s
和之前没有建s_id索引的时间差不多,查看执行计划:
先提取sc再连表,这样效率就高多了,现在的问题是提取sc的时候出现了扫描表,那么现在可以明确需要建立相关索引
CREATE index sc_c_id_index on SC(c_id);
CREATE index sc_score_index on SC(score);
再执行查询:
SELECTs.*
FROM(SELECT*FROMSC scWHEREsc.c_id = 0AND sc.score = 100) t
INNER JOIN Student s ON t.s_id = s.s_id
执行时间为:0.001s,这个时间相当靠谱,快了50倍
执行计划:
我们会看到,先提取sc,再连表,都用到了索引。
那么再来执行下sql
SELECT s.* from Student sINNER JOIN SC scon sc.s_id = s.s_idwhere sc.c_id=0 and sc.score=100
执行时间0.001s
执行计划:
这里是mysql进行了查询语句优化,先执行了where过滤,再执行连接操作,且都用到了索引。
==========
(我是华丽的分割线)
最近又重新导入一些生产数据,经测试发现,前几天优化完的sql执行效率又变低了
调整内容为SC表的数据增长到300W,学生分数更为离散。
先回顾下:
show index from SC
执行sql
SELECT s.* from Student sINNER JOIN SC scon sc.s_id = s.s_idwhere sc.c_id=81 and sc.score=84
执行时间:0.061s,这个时间稍微慢了点
执行计划:
这里用到了intersect并集操作,即两个索引同时检索的结果再求并集,再看字段score和c_id的区分度,
单从一个字段看,区分度都不是很大,从SC表检索,c_id=81检索的结果是70001,score=84的结果是39425。
而c_id=81 and score=84 的结果是897,即这两个字段联合起来的区分度是比较高的,因此建立联合索引查询效率将会更高。
从另外一个角度看,该表的数据是300w,以后会更多,就索引存储而言,都是不小的数目,随着数据量的增加,索引就不能全部加载到内存,而是要从磁盘去读取,这样索引的个数越多,读磁盘的开销就越大。
因此根据具体业务情况建立多列的联合索引是必要的,那么我们来试试吧。
alter table SC drop index sc_c_id_index;
alter table SC drop index sc_score_index;
create index sc_c_id_score_index on SC(c_id,score);
执行上述查询语句,消耗时间为:0.007s,这个速度还是可以接收的
执行计划:
该语句的优化暂时告一段落
总结:
1、mysql嵌套子查询效率确实比较低
2、可以将其优化成连接查询
3、连接表时,可以先用where条件对表进行过滤,然后做表连接(虽然mysql会对连表语句做优化)
4、建立合适的索引,必要时建立多列联合索引
5、学会分析sql执行计划,mysql会对sql进行优化,所以分析执行计划很重要
索引优化
上面讲到子查询的优化,以及如何建立索引,而且在多个字段索引时,分别对字段建立了单个索引
后面发现其实建立联合索引效率会更高,尤其是在数据量较大,单个列区分度不高的情况下。
单列索引
查询语句如下:
select * from user_test_copy where sex = 2 and type = 2 and age = 10
索引:
CREATE index user_test_index_sex on user_test_copy(sex);
CREATE index user_test_index_type on user_test_copy(type);
CREATE index user_test_index_age on user_test_copy(age);
分别对sex,type,age字段做了索引,数据量为300w,查询时间:0.415s
执行计划:
发现type=index_merge
这是mysql对多个单列索引的优化,对结果集采用intersect并集操作
多列索引
我们可以在这3个列上建立多列索引,将表copy一份以便做测试
create index user_test_index_sex_type_age on user_test(sex,type,age);
查询语句:
select * from user_test where sex = 2 and type = 2 and age = 10执行时间:0.032s,快了10多倍,且多列索引的区分度越高,提高的速度也越多
执行计划:
最左前缀
多列索引还有最左前缀的特性,执行一下语句:
select * from user_test where sex = 2
select * from user_test where sex = 2 and type = 2
select * from user_test where sex = 2 and age = 10
都会使用到索引,即索引的第一个字段sex要出现在where条件中
索引覆盖
就是查询的列都建立了索引,这样在获取结果集的时候不用再去磁盘获取其它列的数据,直接返回索引数据即可,如:
select sex,type,age from user_test where sex = 2 and type = 2 and age = 10
执行时间:0.003s ,要比取所有字段快的多
排序
select * from user_test where sex = 2 and type = 2 ORDER BY user_name
时间:0.139s
在排序字段上建立索引会提高排序的效率
create index user_name_index on user_test(user_name)
最后附上一些sql调优的总结,以后有时间再深入研究:
1、列类型尽量定义成数值类型,且长度尽可能短,如主键和外键,类型字段等等
2、建立单列索引
3、根据需要建立多列联合索引
当单个列过滤之后还有很多数据,那么索引的效率将会比较低,即列的区分度较低
如果在多个列上建立索引,那么多个列的区分度就大多了,将会有显著的效率提高。
1、根据业务场景建立覆盖索引只查询业务需要的字段,如果这些字段被索引覆盖,将极大的提高查询效率
2、多表连接的字段上需要建立索引,这样可以极大提高表连接的效率
3、where条件字段上需要建立索引
4、排序字段上需要建立索引
5、分组字段上需要建立索引
6、Where条件上不要使用运算函数,以免索引失效
7、排序字段上需要建立索引
8、分组字段上需要建立索引
9Where条件上不要使用运算函数,以免索引失效
参考文章
http://www.cnblogs.com/linfangshuhellowored/p/4430293.html
慢sql查询
http://tech.meituan.com/mysql-index.html
笛卡尔乘积
http://www.cnblogs.com/Toolo/p/3634563.html
sql优化
http://www.cnblogs.com/mliang/p/3637937.html
http://www.cnblogs.com/xwdreamer/archive/2012/07/19/2599494.html
执行计划参考:
http://www.cnblogs.com/ggjucheng/archive/2012/11/11/2765237.html
原文:https://www.cnblogs.com/tangyanbo/p/4462734.html
SQL执行效率提升几万倍的操作详解!相关推荐
- Android开发效率提升利器-ButterKnife最全使用详解及ButterKnife插件的使用
本文目录 ButterKnife 概述 使用前准备 添加依赖 ButterKnife绑定 Activity绑定 Fragment绑定 RecyclerView Adapter绑定 基本用法 控件注解 ...
- mysql如何分析sql执行效率和进行效率优化
[0]如何分析mysql中sql执行较慢的问题 步骤1.观察,至少跑一天,看看生产的慢sql情况: 步骤2.开启慢查询日志,设置阈值,比如超过5秒钟就是慢sql, 并将它抓取出来: 步骤3.expla ...
- 提高SQL执行效率的几点建议
提高SQL执行效率的几点建议: 1.尽量不要在where中包含子查询 2.关于时间的查询,尽量不要写成:where to_char(dif_date,'yyyy-mm-dd')=to_char('20 ...
- 如何测试sql服务器的性能测试,SQL执行效率和性能测试方法
对于做管理系统和分析系统的程序员,复杂SQL语句是不可避免的,面对海量数据,有时候经过优化的某一条语句,可以提高执行效率和整体运行性能.如何选择SQL语句,本文提供了两种方法,分别对多条SQL进行量化 ...
- SQL Server中通用数据库角色权限的处理详解
SQL Server中通用数据库角色权限的处理详解 前言 安全性是所有数据库管理系统的一个重要特征.理解安全性问题是理解数据库管理系统安全性机制的前提. 最近和同事在做数据库权限清理的事情,主要是删除 ...
- Joomla远程代码执行漏洞分析小白版(小宇特详解)
Joomla远程代码执行漏洞分析小白版(小宇特详解) 今天看了一下2021陇原战役WP,在看web方向的时候,看到pop链,想了解一下,后来又看到了p师傅在15年的一篇文章,在这里记录一下.这里主要是 ...
- 金士顿固态硬盘计算机如何识别,如何辨别金士顿固态硬盘真伪?真假金士顿固态硬盘对比 固态硬盘可以提升电脑开机速度吗【详解】-宝商在线...
由于金士顿这个品牌的名气比较大,尤其在选购内存的时候,绝大数都会选择金士顿这个品牌.不过正是因为品牌名气大,以假冒金士顿品牌为生的小厂商众多.如何辨别金士顿固态硬盘真伪?下面装机之家小编分享一下真假金 ...
- 万兴剪刀手去水印教程_万兴神剪手怎么去水印 神剪手去水印操作详解
万兴神剪手简称为"神剪手",是专为新手设计的视频编辑软件,支持了字幕添加.特效和视频裁剪等功能,那么在手机万兴神剪手怎么去水印呢?想知道的伙伴们快来神剪手去水印操作详解看看吧! 1 ...
- 制作可执行的JAR文件包及jar命令详解
制作可执行的JAR文件包及jar命令详解zt http://www.chinaunix.net 作者:郝君 发表于:2003-08-12 14:32:38 常常在网上看到有人询问:如何把 java ...
最新文章
- [Gitlab]使用Webhook实现前端项目自动发布
- 如何解决 linux socket TIME_WAIT 过多造成的问题(SYN、ACK、FIN、MSL、RST含义)netstat查看TCP连接数命令
- codeforces 711B - Chris and Magic Square(矩阵0位置填数)
- pytorch 入门Tensor(一)
- kubernetes1.8.4安装指南 -- 5. 证书生成
- 深入理解Spring Redis的使用 (五)、常见问题汇总
- 大规模天线阵列(massive-mimo)的介绍
- 提高篇 第五部分 动态规划 第2章 树型动态规划
- Qt文档阅读笔记-Fortune Client Example实例解析
- 云吟职中计算机老师,夹江县云吟职业中学校018招生简介
- EnjoyCSS – 在线的,先进的 CSS3 代码生成器
- 如何为编程爱好者设计一款好玩的智能硬件(十)——无线2.4G通信模块研究·一篇说完...
- Deep Learning经典论文列表(Reading List)
- linux显示防火墙端口命令,Linux查询端口是否被防火墙屏蔽 firewall-cmd命令用法
- 数据库系统概论(第5版)学习笔记第1章 1.1——数据库系统概论
- [5-26]绿色精品软件每天更新[uc23整理]
- 我为什么选择Hexo建站
- 汇编学习软件推荐 - 汇编金手指
- cad断点快捷键_CAD打断命令怎么使用,快捷键是什么
- 在python中输入圆的半_极客起源 - geekori.com - 问题详情 - python动态圆更新糅合到地图显示里...