奇怪的慢sql

我们先来看2条sql

第一条:

select * from acct_trans_log WHERE  acct_id = 1000000000009000757 order by create_time desc limit 0,10
  
第二条:
 select * from acct_trans_log WHERE  acct_id = 1000000000009003061 order by create_time desc limit 0,10
表的索引及数据总情况:
 
索引:acct_id,create_time分别是单列索引,数据库总数据为500w
通过acct_id过滤出来的结果集在1w条左右
 
查询结果:第一条要5.018s,第二条0.016s
为什么会是这样的结果呢?第一,acct_id和create_time都有索引,不应该出现5s查询时间这么慢啊
 
那么先来看执行计划
第一条sql执行计划:

第二条执行计划:

仔细观察会发现,索引只使用了idx_create_time,没有用到idx_acct_id

这能解释第一条sql很慢,因为where查询未用到索引,那么第二条为什么这么快?
看起来匪夷所思,其实搞清楚mysql查询的原理之后,其实很简单
 
我们来看这2条sql查询,都用到了where order by limit
当有limit存在时,查询的顺序就有可能发生变化,这时并不是从数据库中先通过where过滤再排序再limit
因为如果这样的话,从500万数据中通过where过滤就不会是5s了。
 
 
此时的执行顺序是,先根据idx_create_time索引树,从最右侧叶子节点,反序取出n条,然后逐条去跟where条件匹配
若匹配上,则得出一条数据,直至取满10条为止,为什么第二条sql要快,因为运气好,刚好时间倒序的前几条就全部满足了。
 
搞清楚原理之后,我们了解了为什么第一条慢,第二条快的原因,但是问题又来了
为什么mysql不用idx_acct_id索引,这是一个问题,因为这样的话,我们的建立的索引基本失效了,在此类sql下
查询效率将会是相当低
 
因为通过acct_id过滤出来的结果集比较大,有上万条,mysql认为按时间排序如果不用索引,将会是filesort,这样会很慢,而又不能2个索引都用上
,所以选择了idx_create_time。
 
为什么mysql只用一个索引
这里为什么不能2个索引都用上,可能很多人也不知道为什么,其实道理很简单,每个索引在数据库中都是一个索引树,其数据节点存储了指向实际
数据的指针,如果用一个索引来查询,其原理就是从索引树上去检索,并获得这些指针,然后去取出数据,试想,如果你通过一个索引,得到过滤后的指针,这时,你的另一个条件索引如果再过滤一遍,将得到2组指针的集合,如果这时候取交集,未必就很快,因为如果每个集合都很大的话,取交集的时候,等于扫描2个集合,效率会很低,所以没法用2个索引。当然有时候mysql会考虑临时建立一个联合索引,将2个索引联合起来用,但是并不是每种情况都能奏效,同样的道理,用一个索引检索出结果集之后,排序时,也无法用上另一个索引了。
 
实际上用索引idx_acct_id大多数情况还是要比用索引idx_create_time要快,我们举个例子:
select * from acct_trans_log force index(idx_acct_id) WHERE  acct_id = 1000000000009000757 order by create_time desc limit 0,10
耗时:0.057s
可以看出改情况用idx_acct_id索引是比较快的,那么是不是这样就可以了呢,排序未用上索引,始终是有隐患的。
 
 
联合索引让where和排序字段同时用上索引
我们来看下一条sql:
select * from acct_trans_log force index(idx_acct_id) WHERE  acct_id = 3095  order by create_time desc limit 0,10
耗时: 1.999s
执行计划:

 该sql通过acct_id过滤出来的结果集有100万条,因此排序将会耗时较高,所幸这里只是取出前10条最大的然后排序
查询概况,我们发现时间基本消耗在排序上,其实这是内存排序,对内存消耗是很高的。
 

 那么我们有没有其它解决方案呢,这种sql是我们最常见的,如果处理不好,在大数据量的情况下,耗时以及对数据库资源的消耗都很高
,这是我们所不能接受的,我们的唯一解决方案就是让where条件和排序字段都用上索引
 
解决办法就是建立联合索引:
alter table acct_trans_log add index idx_acct_id_create_time(acct_id,create_time)
然后执行sql:
select * from acct_trans_log WHERE  acct_id = 3095  order by create_time desc limit 0,10
耗时: 0.016s

联合索引让where条件字段和排序字段都用上了索引,问题解决了!

 
联合索引使用的原理
但是为什么能解决这个问题呢,这时大家可能就会记住一个死理,就是联合索引可以解决where过滤和排序的问题,也不去了解
其原理,这样是不对的,因为当情况发生变化,就懵逼了,下面我们再看一个sql:
select * from acct_trans_log force index(idx_acct_id_create_time) WHERE  acct_id in(3095,1000000000009000757)  order by create_time desc limit 0,10
耗时:1.391s
索引还是用idx_acct_id_create_time,时间居然慢下来了
执行计划是:
 

看执行计划,排序用到了filesort,也就是说,排序未用到索引。

 
那么我们还是来看看,索引排序的原理,我们先来看一个sql:
select * from acct_trans_log ORDER BY create_time limit 0,100
耗时:0.029s
执行计划为:

这里执行的步骤是,先从索引树中,按时间升序取出前100条,因为索引是排好序的,直接左序遍历即可了

因此,这里mysql并没有做排序动作,如果想降序,则右序遍历索引树,取出100条即可,查询固然快,
 
那么联合索引的时候,是怎样的呢?
select * from acct_trans_log WHERE  acct_id = 3095  order by create_time desc limit 0,10
使用组合索引:idx_acct_id_create_time
这个时候,因为acct_id是联合索引的前缀,因此可以很快实行检索,
如果sql是
select * from acct_trans_log WHERE  acct_id = 3095
出来的数据是按如下逻辑排序的
3095+time1
3095+time2
3095+time3
默认是升序的,也就是说,次sql相当于
select * from acct_trans_log WHERE  acct_id = 3095 order by create_time
他们是等效的。
如果我们把条件换成order by create_time desc limit 0,10呢
这时候,应该从idx_acct_id_create_time树右边叶子节点倒序遍历,取出前10条即可
因为数据的前缀都是3095,后缀是时间升序。那么我们倒序遍历出的数据,刚好满足order by create_time desc
因此也无需排序。
 
那么语句:
select * from acct_trans_log force index(idx_acct_id_create_time) WHERE  acct_id in(3095,1000000000009000757)  order by create_time desc limit 0,10
为什么排序无法用索引呢?
我们先分析下索引的排序规则
已知:id1<id2<id3...  time1<time2<time3....
查询结果集排序如下:
id1+time1
id1+time2
id1+time3
id2+time1
id2+time2
id2+time3
 
索引出来的默认排序是这样的,id是有序的,时间是无序的,因为有2个id,优先按id排序,时间就是乱的了,
这样排序将会用filesort,这就是慢的原因,也是排序没有用到索引的原因。
  
查询计划使用以及使用说明

table:显示这一行数据是关于哪张表的
type:显示使用了何种类型,从最好到最差的连接类型为const,eq_ref,ref,range,index,all
possible_keys:显示可能应用在这张表中的索引。如果为空,没有可能的索引
key:实际使用的索引,如果为null,则没有使用索引。
key_len:使用的索引的长度。在不损失精确性的情况下,长度越短越好
ref:显示索引的哪一列被使用了,如果可能的话,是一个常数
rows:mysql认为必须检查的用来返回请求数据的行数

sql查询调优之where条件排序字段以及limit使用索引的奥秘的更多相关文章

  1. sql查询结果集根据指定条件排序的方法

    oracle认为 null 最大. 升序排列,默认情况下,null值排后面. 降序排序,默认情况下,null值排前面. 有几种办法改变这种情况: (1)用 nvl 函数或decode 函数 将null ...

  2. SQL Server调优系列基础篇(子查询运算总结)

    前言 前面我们的几篇文章介绍了一系列关于运算符的介绍,以及各个运算符的优化方式和技巧.其中涵盖:查看执行计划的方式.几种数据集常用的连接方式.联合运算符方式.并行运算符等一系列的我们常见的运算符.有兴 ...

  3. SQL Server调优系列玩转篇(如何利用查询提示(Hint)引导语句运行)

    前言 前面几篇我们分析了关于SQL Server关于性能调优的一系列内容,我把它分为两个模块. 第一个模块注重基础内容的掌握,共分7篇文章完成,内容涵盖一系列基础运算算法,详细分析了如何查看执行计划. ...

  4. SQL Server调优系列基础篇 - 子查询运算总结

    前言 前面我们的几篇文章介绍了一系列关于运算符的介绍,以及各个运算符的优化方式和技巧.其中涵盖:查看执行计划的方式.几种数据集常用的连接方式.联合运算符方式.并行运算符等一系列的我们常见的运算符.有兴 ...

  5. SQL Server 调优系列玩转篇一(如何利用查询提示(Hint)引导语句运行)

    前言 前面几篇我们分析了关于SQL Server关于性能调优的一系列内容,我把它分为两个模块. 第一个模块注重基础内容的掌握,共分7篇文章完成,内容涵盖一系列基础运算算法,详细分析了如何查看执行计划. ...

  6. SQL Server 调优系列基础篇 - 子查询运算总结

    前言 前面我们的几篇文章介绍了一系列关于运算符的介绍,以及各个运算符的优化方式和技巧.其中涵盖:查看执行计划的方式.几种数据集常用的连接方式.联合运算符方式.并行运算符等一系列的我们常见的运算符.有兴 ...

  7. SQL Server调优系列进阶篇(查询语句运行几个指标值监测)

    前言 上一篇我们分析了查询优化器的工作方式,其中包括:查询优化器的详细运行步骤.筛选条件分析.索引项优化等信息. 本篇我们分析在我们运行的过程中几个关键指标值的检测. 通过这些指标值来分析语句的运行问 ...

  8. SQL Server调优系列基础篇(常用运算符总结——三种物理连接方式剖析)

    前言 上一篇我们介绍了如何查看查询计划,本篇将介绍在我们查看的查询计划时的分析技巧,以及几种我们常用的运算符优化技巧,同样侧重基础知识的掌握. 通过本篇可以了解我们平常所写的T-SQL语句,在SQL ...

  9. SQL Server调优系列基础篇(索引运算总结)

    前言 上几篇文章我们介绍了如何查看查询计划.常用运算符的介绍.并行运算的方式,有兴趣的可以点击查看. 本篇将分析在SQL Server中,如何利用先有索引项进行查询性能优化,通过了解这些索引项的应用方 ...

随机推荐

  1. (译)Windsor入门教程---第三部分 编写第一个Installer

    原文:http://docs.castleproject.org/Windsor.Windsor-tutorial-ASP-NET-MVC-3-application-To-be-Seen.ashx ...

  2. (简单) POJ 3984 迷宫问题,BFS。

    Description 定义一个二维数组: int maze[5][5] = { 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, ...

  3. WKWebKit基础

    UIWebView & UIWebViewDelegate 这个两个东西在 WKWebKit 中被重构成 14 个类 3 个协议. WKWebKit Framework Classes WKB ...

  4. 升级apache

    升级Apache到最新版本,本来并不复杂,但是因为涉及到不能停止现有的Apache实例的运行,因此要小心翼翼的做. 大致分成三步: 编译新的Apache, 配置新的Apache, 替换旧的Apache ...

  5. STM32串口寄存器操作(转)

    源:STM32串口寄存器操作 //USART.C /************************************************************************** ...

  6. java学习(四) excel读取

    private static void readExcel() { String filePath = "C:/Standardzid.xls"; File file = new ...

  7. 关于Apache,Mysql,PHP之间的关系

    声明:以下为作者原创,转载请注明文章来源地址. 通过百度百科我们知道 Apache(全称Apache HTTP Server):是世界使用排名第一的Web服务器软件.可以在大多数计算机操作系统中运行, ...

  8. 2)Java学习笔记:匿名内部类

    为什么要使用匿名内部类 ①如果以前的类有一些缺陷,只是想在某一个模块进行修复,可以在引用该类的地方使用匿名内部类,在overRide方法进行修复. ②如果一个类,需要派生出很多类,而且这些类大多只是在 ...

  9. Struts2接受参数的几种类型和接受复杂类型参数(list<String>和list<Object>)

    Struts2接受参数的几种类型 大概有这几种类型: 1.使用Action的属性接受参数 在Action中加入成员变量,配置Getter和Setter方法,Getter而和Setter方法的名字和表单 ...

  10. TSP问题 遗传算法 智能优化算法

    写了半天,效率还是有点低的,以后有空再优化下: //用次序表示法来表示个体编码 #include<iostream> #include<fstream> #include< ...