奇怪的慢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. 导出WAS已部署的ear包的几种方法

    可以通过下面几种办法将部署好的工程导出为一个ear包. 1.最简单的,通过was的控制台导出: 首先登录控制台,进入"企业应用程序"管理页面,选中要导出的工程,点击"导出 ...

  2. (五)Jquery Mobile列表

    Jquery Mobile列表 一.JM列表 1.普通列表            效果:            带序号的列表 将ul换成ol      效果:       2.data-inset=& ...

  3. hadoop+海量数据面试题汇总(二)

    何谓海量数据处理? 所谓海量数据处理,无非就是基于海量数据上的存储.处理.操作.何谓海量,就是数据量太大,所以导致要么是无法在较短时间内迅速解决,要么是数据太大,导致无法一次性装入内存. 那解决办法呢 ...

  4. 关于eclipse创建Maven项目创建的问题

    1.问题: 为什么Maven Update Project JDK变回1.5 解释:官方文档 The Compiler Plugin is used to compile the sources of ...

  5. LPC1768IAP(详解,有上位机)

    之前说了stm32的iap编程,今天天气真好,顺手就来说说lpc1788的iap编程(没看前面的请查看stm笔记下的内容) 首先是flash的算法,lpc1768并没有寄存器来让我们操作flash,他 ...

  6. ucos移植指南

    指定堆栈数据类型(宽度) typedef unsigned int OS_STK; 指定Ucos移植方法3中保存cpu状态寄存器的变量的宽度 typedef unsigned int OS_CPU_S ...

  7. iOS调用相机,相册,上传头像 分类: ios技术 2015-04-14 11:23 256人阅读 评论(0) 收藏

    一.新建工程 二.拖控件,创建映射 三.在.h中加入delegate @interface ViewController : UIViewController 复制代码 四.实现按钮事件 -(IBAc ...

  8. 安装arm-linux-gcc交叉编译器

    1.开发平台 虚拟机:VMware 12 操作系统:Ubuntu 14.04 2.准备交叉编译工具包(arm-linux-gcc-4.5.1) 编译uboot和linux kernel都需要gnu交叉 ...

  9. ios数据存储——对象归档

    归档:数据从内存与闪存相互转化,类似“序列化”,将数据转换成二进制字节数据 操作:有两种方式,第一种是单个对象作为root进行归档和恢复,一个对象一个文件:第二种,可以同时归档多个对象到一个文件 注意 ...

  10. java设计模式面试

    设计模式 1. 单例模式:饱汉.饿汉.以及饿汉中的延迟加载,双重检查 2. 工厂模式.装饰者模式.观察者模式. 3. 工厂方法模式的优点(低耦合.高内聚,开放封闭原则) 单例模式 分类:懒汉式单例.饿 ...