高性能mysql优化---索引优化
Posted zhuxineli
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了高性能mysql优化---索引优化相关的知识,希望对你有一定的参考价值。
1 建表原则
<1>动静分离:定长和变长相分离。也就是说占用字节相同的字段尽量放在同一个表中,这样的好处是在查找的时候效率会非常非常高,不仅仅是索引的原因,甚至可以通过计算占用空间大小来进行快速检索。
<2>常用字段和不常用字段分离
<3> 适当添加冗余来提高检索速度(减少联表,但对于可以预想到经常变的字段不适合添加冗余)
2 列类型的选择
整形 > date,time > enum char > varchar > blob,text
3 索引
<1>索引是为了快速检索而建立的一种数据结构,为什么索引快?以b-tree为例,是因为索引的层数少,导致了检索速度快,也就是用了二分查找,比如说一个有40亿条数据的表,如果不建立所以的话查找一条数据的平均检索次数是20亿次,但如果建立了索引,最多会检索32次,2的32次方即为40亿多,脑补一下树的结构。
常见索引使用误区:
<1.1>在where条件的常见列上都添加索引,一条语句中独立的索引只能用到一个,所以我们应使用联合索引
<2>hash索引
hash比b-tree更快,理论上查询一次就可以,不论有多少数据量,但无法进行范围查询这是致命弱点
<3>聚簇索引
InnoDB类型的索引即为聚簇索引,所有数据都在叶子节点(通过主键关联)下。
<4>非聚簇索引
myisam 类型即为非聚簇索引。
关于聚簇索引和非聚簇索引引起的不同及其重要性
一个经典的关于索引的问题:
create table test(
id varchar(64) primary key,
var int
)
在id var上有联合索引,10000条数据
问题:
select id from test order by id 非常慢
select id from test order by id,var 非常快————why why why???
补充一点:在该表中还有几个字段,都是超大的文本,字段类型均为text(3000)
结合聚簇索引、非聚簇索引的知识点
首先,我们分析一下这是一个什么类型的表。因为Myisam是非聚簇索引,sql1 和 sql2的查询分别用到的主键索引和联合索引都是单纯的索引树,和数据没有关系在这一方面应该不会对检索速度造成很大影响。如果是聚簇索引的话呢?因为所有的数据元素都在主键索引的叶子节点下(而这里的的数据元素又很大),也就是说当使用到主键索引的时候需要拖着沉重的叶子来回爬数据,而第二条sql语句,使用到的是二级索引,没有数据的拖累(这一点类似于myisam),所以要快很多。
也就是说,这和是否在主键上建联合索引没有关系,只要使用的是二级索引都会快很多(前提是数据元素超级大的时候)。也和主见是否不是我们常用的int类型,是否自增没有关系
4 索引覆盖
减少回行操作,不用再去磁盘查找(磁盘需要去磁盘查找),节省时间。
5 理想的索引
1 查询频繁
2 区分度高
3 索引长度尽量小
4 尽可能的覆盖到常用查询字段(也就是覆盖索引)
关于如何选取索引长度问题,原则肯定是区分度越高越好, 但长度越短越好。
select count(distinct (left (field,1)))/count(*) select count(distinct (left (field,2))) select count(distinct (left (field,3)))
分别计算区分度,区分度应该越接近于1越好,但越短越好,所以要找一个两者兼得的值。
这里有一个小知识点:
6 伪哈希
我们可能会遇到这种情况,需要在一个字符串上建立索引(上面的问题就是啊!),现在的问题是上面的问题我们遵循的是最左前缀原则,也就是说选择了区分度较高的长度,但如果有这么一列数据,前面几个字符串甚至是几十个字符串都是相同的,那怎么办?加到索引中吧,浪费。不加吧,无法达到简历索引的效果。比如存储域名的字段http://www前面几乎都是这样的字符串。这里提供两种解决思路
<6.1>将字符串倒叙存储,然后依据最左前缀原则建立索引
<6.2>将该字段crc32加密一下,存它的加密后的内容,不会重复而且是整型,检索更方便——-这就是伪哈希
7 多列索引
要考虑的因素,列的查询频率和列的区分度、列的查询顺序,平常的话我们考虑更多是的是列的区分度,区分度高的在最左边。一定要结合实际业务场景
8 索引与排序
对于覆盖索引,直接在索引上查询时,就是有顺序的 using index
对于排序,正确取出的数据就是有序的,不用再去重新排序。如果实在没办法,就会使用到using filesort,使用临时表排序,这个过程可能是在内存也可能在磁盘。
对于mysiam而言,数据是数据,索引是索引。如果where条件和orde by 条件用的条件不一致,会产生filesort排序,要防止使用using filesort
9 重复索引 冗余索引
比如,a b 和b a就是冗余索引(没有重叠),但效果确实很好。a b 和a就是重复索引,没有好处,只用ab 就可以
10 修复表 optimize table tablename
explain https://blog.csdn.net/zhuxineli/article/details/14455029
11 in子查询陷阱
众所周知在使用in的时候是会使用到索引的(但数量太多就不会用到了,这个我没有得到一个准确或大致的范围到底多少个以上就不能使用索引了),且看下面这条语句。表结构说明
goods商品表,主要字段
goods_id goods_name cat_id
商品主键 商品名称 商品分类
cat_id有索引
catgory分类表,主要字段
cat_id cat_name partent_id
分类主键 分类名称 上级id
partent_id有索引
这时我们想找出所有cat_id为1的分类下的商品我们可以这样
select * from goods where cat_id = 1,但这时候如果cat_id = 1的分类是一个大分类,它下面有三个小分类分别是2,3,4我们可以这样
select * from goods where cat_id in (select cat_id from category where partent_id = 1)
很简单的一条语句,我们先推测一下它是怎么执行的吧!!!
首先,先执行子查询语句,找出cat_id为2,3,4三条记录,然后代入到外查询,就变成了
select * from goods where cat_id in (2,3,4) 因为在goods表中的cat_id和category表中的partent_id都有索引,所以这两条语句都会使用到索引,速度还挺快呢!!!那答案是不是我们预期的呢?no 不是这样的,看来着是我们的一厢情愿啊,因为in型子查询的运行机制不是这样的。 这里我先说一下in型子查询的运行机制,然后我们倒推出索引使用情况,看能不能分析正确,锻炼下自己
首先是,外层sql会扫描全表,挨个找到数据,放到子查询中去对比,那子查询的语句会变成什么样呢?select partent_id from catgory where cat_id = 2,注意这里的cat_id是外查询中搜出来的结果,所以猜测:外查询不会用到索引,全表扫描。内查询会使用到主键索引,且会使用到覆盖索引。
15 limit分页优化
关于如何使用索引以及一条sql语句为什么会使用到索引
表结构说明:table表,id是自增主键,有1千万条数据
select * from table limit 5000000,10
select id from table limit 5000000,10
select * from table order by id limit 5000000,10
select id from table order by id limit 5000000,10
让我们来分析下每条语句运行时的索引使用情况
第一条语句:这是我们很常见的分页语句,不同的是在limit offset,pagesize中当offset小的时候是没有问题的,当offset大的时候速度会骤降,为什么会出现这种情况?通过explain我们可以知道索引时候用情况。当我们通过explain分析的时候,它的type是all,也就是扫描全表了,影响行数在5000000以上,(注意:执行时间要看服务器性能而定,如果你的内存很大并且执行该语句时负载很低可能时间不会很长,但仍然是不可接受的),所以在这里我没有给出一个具体的执行时间,但总之执行时间是我们不能接受的。此外,key列为空,说明没有使用到索引,也就是说它不是按照我们想象的,先找到5000000这条数据,然后往后找10行数据,不是这样的,可以这么理解,它是从0开始找到5000010条数据,然后扔掉前面的5000000行,只保留你想要的那10条数据,就是这么任性,欠揍,气人。
第二条语句:情况好了很多,explain执行结果type为index,key为primary,rows仍然是5000000以上。但至少说明了两点。一:这里虽然影响行数仍然很大但是是沿着索引树跑的(因为type是index),也就是跑的索引树的全部(而不是扫的全表)。单纯这一步就要比扫全表快很多了。二:既然是沿着索引树跑,那用的那个索引呢?key表示用的是主键索引。所以这条语句的耗时要比第一条语句小很多,可以接受,但效果也不会特别理想因为毕竟是扫的整个索引树而不是直接定位
总结一下:我们看了上面两条语句的索引使用情况,但这两条语句只是搜索内容的不同,为什么会导致索引使用情况有如此大的不同呢?按照常理推测,即使是多搜了几个字段,执行时长也不应该相差一个量级啊!!!让我们先来分析第二条语句,mysql优化器是这么想的:这条语句只搜索id,而id又是主键,如果我们使用索引的话就不用回行了,你想要的数据直接就是索引数据,直接搜出来给你就可以了。而第一条语句mysql优化器一看:要全部的数据,不论我用哪一个索引都会回行,反正我需要找到5000000多条数据然后扔掉5000000个,还不如不适用索引。由此,也推测出了,limit语句在使用过程中不是先定位到5000000这条数据在往后找10条,而是直接找到500000010个,然后扔掉前面的50000000个。任性,欠揍,气人
第三条语句:和第一条很像,不同的是用了order by id.先不去看explain的执行结果,让我们先预测一下他的索引使用情况:经过刚才的分析我们知道了,limit的运行机制,所以我们推断是想找到50000010条数据,然后排序,然后扔掉前5000000个,所以这条语句仍然不会使用到索引,反而会排序,而且因为数据量比较大,所以会在磁盘上排序,所以会用到using filesort,速度会更慢。那explain的执行结果是什么呢?type是all,key是空,extra是using filesort
第四条语句:这条语句和第二条就一模一样了吧,因为第二条语句本身是沿着缩阴术跑的,而索引树本身就是排序的,所以就不存在取出数据在排序这一步了,取出来的数据就是已经排好序的,执行时长和第二条语句也应该会在一个量级。explain分析结果type index,key primary,extra using index ,rows 5000000以上
以上只是个人理解,哪里说的不对的,望指教
ok,经历了上面的分析,下面我们来看一下如何优化大数据量下的分页优化
原理想必此时你也已经知道了,单靠原始的limit 肯定是不行的了,谁也受不了扫全表过程,那想让他快的话就要使用索引,使用索引的目的就是先快速定位到第5000000条数据,然后往后找10条数据,那如何利用索引定位到第5000000条数据呢?
select * from table where id > 5000000 limit 10
这是一种办法,但很明显这里有局限性,只能在id是连续不能有删减的情况下使用。如果删减了话,找到的数据其实就不是第5000000页的数据了,而是单纯的id大于5000000往后的10条数据,而我们想要的数据极有可能是id号为5000004,50000005,50000006这样的
其实上面我们已经几乎找到了解决办法,就是第二条和第四条语句,因为这时候已经把想要的10条id给取出来了,这时候我们只需要用一下临时表给关联一下就ok了
select * from table as a join (select id from table limit 5000000,10) as b on a.id = b.id
select * from table as a join (select id from table order by id limit 5000000,10) as b on a.id = b.id
同理,既然我们知道了原理是先快速定位到第5000000这个基本点,那还有没有其他的变形语句呢?看看下面这条语句
select * from table where id > (select id from table order by id limit 5000000,1) limit 10
原理是不是一样呢?甚至说,这个更符合我们的预期!!!
良心推荐
select * from clazzfeedback limit 5000000,10;
# 执行时间2秒左右 type all ,key null ,extra null 全表扫描,没有使用索引。
select classfeedbackid from clazzfeedback limit 5000000,10;
#执行时间0.8秒,type index,key campusid,extra using index
select * from clazzfeedback order by classfeedbackid limit 5000000,10;
#执行时间2秒左右,type index,key primary,extra null
select classfeedbackid from clazzfeedback order by classfeedbackid limit 5000000,10;
#执行时间0.9秒左右,type index,key primary,extra using index
以上数据是我在我们服务器上执行的结果并给出explain执行结果
done,完成
16 关于索引优化一些小tips
有时候我们在遇到慢sql的时候,不要一上来就说要优化索引,业务逻辑千变万化有时候还真不是索引用的不好,除了下面这种显而易见的是搜索结果太多导致的问题select * from table limit 0,100000这种语句一下子就能看出问题所在,真不是你索引建的不好或sql语句写的不咋地,而是业务逻辑本身就有问题,你一次性要10万条数据,谁也受不了啊!但还有这种问题导致的变形问题,比如selct * from table where field1= 1 and field2 = 2(实际情况可能要比这复杂的多).如果运维扔给你这么个语句,说这条语句慢死了,要你优化,你拿到之后运行了一下,发现20秒过去了,还没有结果,然后不不敢继续执行了,停止执行。用explain一分析,影响行数竟然快到了扫全表的级别,索引倒是用了,但很明显没啥乱用,这时候是不是想着马上要给field1 field2建一个联合索引呢?我一个同事遇到这个问题的时候首先想到的就是马上建一个联合索引(因为时间紧急,线上的数据库都快挂了,似乎已经没时间了解业务逻辑),但根据我的经验来看,我没有马上动手!为什么呢?只有一个原因,因为这块逻辑已经上线近2年的时候,代码也没人动过,可以说基本上经得起考验的了,应该不是索引利用方面的问题,那是为什么呢?拿到测试环境下一执行,38秒,结果集达到了10条数据,说明什么?说明不是索引用的不好,而是业务逻辑本身要求的结果集太大,这时候我们的出发点就不在是索引优化而是业务逻辑的优化,经排查得知,原来是该问题自从上线以来一直存在,只不过用户行为习惯不会触发这样的sql语句从而导致一直没出现过这个问题,很明显这样的业务逻辑是错误的(谁会一次性检索10万条数据,报表除外)这样我们就可以从业务逻辑层面来解决这个问题了!!!
17 接下来的是极为沉重的话题,搞不好就能理解索引的本质,那就是关于order by 如何使用到索引以及为什么能够使用到索引
select id,name from table order by id desc
其中id是主键,存储引擎分别是myisam和innodb的时候,来分析下索引的使用情况以及是否使用了using filesort
在这里mysiam & innodb的区别是什么?难道是一个支持事物一个不支持事物吗?当然不是,区别是前者是非聚簇索引后者是聚簇索引,so 下来分析innodb时,很明显如果没有order by语句的话这时候是要扫全表的,这时候有了order by 子句还是以id主键排序的,又因为聚簇索引的原因(所有数据都在主键下(默认情况))这时候可以直接用到主键索引取出所有数据而且是排好序的,多方便啊!于是便使用了主键索引,并且未使用using filesort。而myisam呢?虽然仍然可以使用索引来获取排好序的数据,但仅限于获取到id(因为非聚簇索引的原因),另一个字段name还是需要通过回行来取出来的,这时候我才mysql优化器内心是这么想的(仅限于我的猜想):这时候我有两种方法满足需求 1 通过使用索引获取到有序的id然后回行获取name值 2 直接扫全表获取两个字段然后直接排序。可能在mysql内心深处觉得第二种方法比第一种方法更快,所以使用了这种方法(我测试的时候数据量只有10000条),可能数据多了的时候第一种方法又会有优势吧!!!我内心深处还是觉得第一种方法快的。
select * from table where name = 'awesome' order by id name有索引
这时候会用到索引吗?排序情况如何?
首先这时候会用到name索引,这是没有问题的。那order by是否会用到索引的排序呢?这么理解一下:索引是什么,索引是一种排好序的数据类型,套用在这里就是我们使用where的时候用到了name索引快速定位到name值为awesome的所有数据,但这时候取出来的数据是只按照name排序的,我利用索引取出来的数据已经完成我这个索引的工作了,剩下的你想按照什么条件排序我这个索引就无能为力了!!!!理解一下,是不是想了什么!!所以的原理就是这样的,再次划重点索引是为了加快检索而建立一种排好序的数据类型 所以说你看下面这种情况会使用到索引吗?
select * from table where name = 'awesome' order by id ------name 和id 建有联合索引
这时候通过索引name查找到数据也是没有问题的,然后查出来的数据-----额,我换一种思路,来倒推一下联合索引的原理
这时候会用到索引排序,不使用using filesort,所以这个时候你又理解了联合索引的原理
首先,这时候通过索引找到name值为awesome的数据是没有问题的,这时候又用到了索引排序,说明联合索引是这样的,假如说name和id的值分别为1 2 3 4 5 6 7 8 9,如果单列索引的话肯定是 1 2 3 4 5 6 7 8 9这样排序保存的,而联合索引是这样的
1+1 1+2 1+3 1+4 1+5 1+6 1+7 1+8 1+9
2+1 2+2 2+3 2+4 2+5 2+6 2+7 2+8 2+9
3+1 3+2 what the fuck,写的真累
正因为如此,我们通过name=‘awesome’获取到了相应数据,然后呢,剩下的是其实已经是按照id排好序的了。所以说,联合索引有用,但更占空间,相应的在插入或更新数据的时候也会因为改变树的结构而导致速度变慢,但这就是用空间换时间啊(绝大多数业务系统的插入和更新是远小于检索的频率的,所以我们可以接受插入、更新、删除时的所谓的慢)
ok,让我们在稍微拓展一下,(突然插个梗,好多人说mysql一条语句只能用一个索引(单列索引或联合索引)不是这样啊,高版本的mysql已经可以使用多个索引的啦,具体啥版本开始支持的我不知道,反正我用的5.6支持)
上面这种情况我们是检索所有字段,那如果我们只检索name呢?有什么区别吗?这时候会用到覆盖索引,因为我们想要检索的字段就是索引的数据,不需要再去回行获取了,所以速度又要快一点,虽然覆盖索引的道理很简单,但真的很有用处。
what,不是要讲order by的吗,怎么感觉跑偏了???其实没有其实通过难一点的知识点才能更好的理解任何一门学科的原理。order by什么情况下会使用到索引肯定是有公式可寻的,但我们最好是了解它的原理而不是只是套用公式
在排序操作中如果能使用到索引来排序,那么可以极大的提高排序的速度,要使用索引来排序需要满足以下两点即可。
1、ORDER BY子句后的列顺序要与组合索引的列顺序一致,且所有排序列的排序方向(正序/倒序)需一致
2、所查询的字段值需要包含在索引列中,及满足覆盖索引(限于myisam引擎,看来mysql认为回行的代价有点高啊)
以上是关于高性能mysql优化---索引优化的主要内容,如果未能解决你的问题,请参考以下文章