Mysql查询性能优化

      Mysql查询性能优化要从三个方面考虑,库表结构优化、索引优化和查询优化。一般在实际应用中,咱们要面对这三种搅和一块儿的状况。mysql

1、 库表结构优化web

      良好的逻辑设计和物理设计是高性能的基石。库表结构的设计既要关注全局,又要专一细节。要设计优秀的库表结构,可从如下几个方面着手:sql

1.  选择优化的数据类型数据库

         选择优化的数据类型能够遵循如下几个原则:api

         更小的一般更好,应该尽可能选择正确数据类型的最小数据类型,更小数据类型一般更快,由于它们占用更少的磁盘、内存和cpu缓存。缓存

         简单就好,简单的数据类型须要更少的cpu周期,好比整形比字符串操做代价更低。应该使用mysql内部类型存储时间类型,使用整形存储ip地址。性能优化

         尽可能避免null,尤为是添加索引的列。可为null的列,对mysql来讲更难优化,可为null的列使用索引、索引统计和值比较都更复杂。可为null的列须要更多的存储空间,能够null的列被索引时,每一个索引记录须要一个额外的字节,在myisam中甚至还可能致使固定大小的索引(列如只有一个整数列的索引)变成可变大小的索引。可是若现有的schema含有可为null列,一般不要首先考虑改掉这种状况,由于,一般修改这种状况对性能的提高不明显。服务器

针对mysql中各个具体的数据类型作优化处理:mysql优化

1) 整数类型数据结构

         Mysql中的整数类型包括tinyint、smallint、mediumint、int、bigint,分别使用八、1六、2四、32和64位空间存储,它们能够存储的范围为-2n到2n-1,其中n表示位数。整数类型有可选的unsigned属性,表示不容许为负值,大体可使用正数的范围提升一倍。Mysql能够为整数指定宽度,列如int(11),这个宽度不会限制值的合法范围,只是规定了mysql的一些交互工具(命令行客户端)用来显示字符的个数,对于存储和计算来讲没有任何做用。

2) 实数类型

         实数就是带有小数点的数据类型,mysql中的实数类型包括:float、double和decimal。其中,float和double属于浮点类型,是常见的数据类型,存储空间分别占用4和8个字节。Decimal能够指定精确的整数和小数位数,用于存储精确的数值,在财务中的应用普遍。Cpu没法直接执行decimal的计算,可是mysql内部实现了decimal的计算。可使用decimal存储比bigint更大的整数。

3) 字符串类型

        Mysql中字符串类型包括varchar和char,分别用于存储可变长字符和固定长度字符。

4) Blob和text类型

         Blob与text是用来存储大数据的数据类型,分别采用二进制和字符方式存储,与oracle中blob与clob(clob也是二进制存储,不是字符方式)相似。Mysql对blob和text列进行排序的方式与其它类型不一样,它只对每一个列的最前max_sort_length字节而不是整个字符作排序。Blob和text列不能添加索引。

5) 日期和时间类型

       Mysql中包含datetime和timestamp两种存储日期和时间的类型。Datetime能够存储大范围的值,从1001到9999年,精确到秒,与时区无关,使用8个字节存储空间,显示格式为“2018-02-27 10:44:22”。Timestamp保存范围从1970到2038,显示时间依赖时区。一般尽可能使用timestamp类型,由于它比达特time的空间效率更高。另外对于须要精确到毫秒的时间存储,能够先将它们转换成整数后保存。

6) 位数类型

      位数类型包括bit和set,使用很少,不建议使用,若要存储boolean型数据,建议转换为tinyint存储。

7) 选择标识符

      标识列通常用来做为表的主键或在关联操做中使用,在mysql中标识列最好采用整数,能够利用它的auto_increament属性。对于字符串的标识列,若是可以避免尽可能避免,所以字符串须要更大的存储空间,关联操做慢,myisam对字符串使用压缩索引,会致使查询很慢。若是存储uuid值时,尽可能去除“-”符号,或者用unhex()函数转换uuid为16字节的数字,而且存储在一个binary(16)列中。检索时能够经过hex()函数来格式化为十六进制格式。具体理解下这个两个函数的使用??

2.  Mysql schema设计中的陷阱

      表的设计不能有太多的列,数千的列会影响性能,mysql的存储引擎api工做时须要在服务器层和存储引擎之间经过行缓存格式拷贝数据,而后在服务器层将缓存内容解码成各个列。从行缓存中将编码过的列转换成行数据结构的操做代价是很是高的,转换的代价依赖于列的数量。关联操做设计的表不要太多,不然执行会很慢,mysql限制了每一个关联操做最多只能有61张表,在实际应用中,经验法则得出关联操做的表数量最好控制在12个之内。

3.  范式和反范式

       在初学数据库设计时,咱们每每要遵循数据库的范式要求,尤为是前三个范式。严格遵循范式设计的表一般更小、数据冗余少,作更新操做简单快捷,可是,惟一的缺点就是在作查询时须要表关联,关联查询会不只会带来高的代价,并且还可能形成索引策略失效,致使更低效率的查询。绝对的范式化是实验室中的产物,在实际的应用中要混用范式化和反范式化,根据具备状况,每每会带来较高的查询效率。

4.  缓存表和汇总表

       有时提高性能最好的方法是将衍生的冗余数据保存到缓存表或汇总表,而后执行查询这些表便可得出所须要的数据。这里缓存表表示存储那些从其它表获取(可是每次获取的速度很慢)数据的表,而汇总表保存的是使用group by 语句聚合数据的表(数据不是逻辑上冗余),也能够把这些表称为累积表。好比咱们要统计某网站24小时以内发送的信息数量,那么从相关表进行统计就会很慢,若是咱们每个小时就把进行统计一次,并把统计结果存放到一张表中,当咱们须要统计24小时以内发送的信息数量时,只须要执行简单地查询就能获得须要的结果,这样就极大地提升的查询效率,可是缺点是数据不是100%精确。

       对于衍生数据保存的另一种方式是使用物化视图,物化视图能够增量地从新计算其内容,不须要经过查询原始表来更新其数据。

       计数器表在web应用中很常见,例如记录每一个用户的发送信息的数量、下载文件的数量,利用计数器表能够很简单的获取我的的记录数量,比直接从相关数据表中统计的效率高不少,好比一张计数器表,记录网站的点击数量,若只有一行记录,那么在条记录上只能存在一个排它锁,更新查询操做等只能串行进行,影响并发,若将记录保存在多行上,每次随机选择一行进行更新,那么并发性能就会大幅提升。多行记录与单行记录的表设计,要参照具体的应用。

2、 索引优化

     索引应该是对查询性能优化最有效的手段了,它可以轻易地将查询效率提升几个数量级。

1.  索引类型

     索引类型包括:b-tree索引、哈希索引、空间数据索引和全文索引等。

     B-tree索引采用b-tree方法实现索引,存储字段值,并且索引列是顺序组织存储的,适合order by、范围查找等操做。哈希索引存储的是字段值的哈希值,并非真正的字段值,另外哈希索引无序,不适合作order by或范围查找等操做。空间索引是用来作地理数据存储,可是mysql对地理信息的支持并非太好,最好是使用postgis。全文索引是一种特殊的索引类型,它查找的是文本中的关键词,而不是全值对比,在同一列上能够建立全文索引和基于值b-tree索引,不会相冲突,全文索引使用于matchagainst,而不是普通的where操做。

2.  索引策略

     了解索引类型后,正确地建立和使用索引是提升查询性能的基础。

1)  独立的列

     独立的列就是索引列应该是单独使用不能做为表达式的一部分或函数的参数,例以下面的例子:

Select actor_idfrom actor where actor_id+1=5;

Select actor_idfrom actor where to_days(current_date)-to_days(date_col)<5;

这两种方式均没法使用索引。

2)  前缀索引

      有时候须要在很长的字符列上添加索引,这样索引就会很大,会形成很慢。最好的方式就是使用字符串的部分前缀建立索引,前缀索引能够大大减小索引空间,提升索引效率。对于blob、text或很长的varchar类型列必须使用前缀索引,由于mysql不容许索引这些列的完整内容。前缀索引的缺点是mysql没法使用前缀索引作order by和group by,没法覆盖扫描。在建立前缀索引时,要保证选择足够多的前缀,以保证较高的选择性,同时又不能太长以节约空间。

3)  多列索引

      多列索引是相对单列索引来讲的,单列索引就是在单个列上建立索引,多列索引就是在多个列上建立索引。Where后面有多个条件时,多列索引每每比单列索引更有效。例如,

Select * fromactor where actor_id=1 or film_id=0;

虽然mysql在执行sql时,会执行一个“索引合并”的优化,可是这只是优化的结果,说明了表上的索引建立的很糟糕。

3、 查询优化

1. 重构查询方式

1) 切分查询

         主要用于删除信息时,一次性删除大量数据时,则可能会须要锁住不少数据、占满整个事务日志,耗尽系统资源,阻塞不少查询。所以对于影响不少数据的delete语句,切分红多个较小的delete语句,可以快速执行完成,大大减小删除持有锁的时间,下降对mysql性能的影响。

2) 分解关联查询

         有时候一个大的关联查询,可能会很慢,如何把这个关联查询分解成多个单表查询,而后在应用中对数据执行关联操做。Mysql能够实现单表查询缓存,所以多个单表查询,能够有效利用查询缓存。单表查询能够减小锁的影响。将查询的数据在应用中关联,能够更容易对数据库拆分,实现数据库的高性能和可扩展。能够减小冗余记录的查询,并且在应用中作关联,可以数据之间的哈希关联,避免在mysql中的嵌套循环查询,效率就会大幅提升。

2. Mysql查询优化器的限制

         服务器收到sql后,对其进行解析、预处理,并由查询优化器处理生成对应的执行计划。查询优化器就是进一步对sql进行优化处理,使其执行速率更高。可是对一些特定的sql语句的写法,查询优化器是有局限的,并不能实现优化处理。所以,在书写sql语句时,要避开这些写法,下面是对查询优化器有局限的sql写法分类:

1)  关联子查询

         使用in()方法加子查询,好比下面sql语句:

Select * from filmwhere film_id in(select film_id from film_actor where actor_id=1);

优化器会把该sql语句改为以下:

Select * from filmwhere exists(select * from film_actor where actor_id=1 and film.film_id=

film_actor.film_id);

因为子查询关联到外部表,须要用到外部表的film_id,所以这个sql没法先执行子查询,只能先执行外部表的查询,找到film_id,根据film_id执行子查询语句。若是外部表数据很是大,那么这个sql执行时间就会很长。所以,使用in()加子查询,性能常常会很低,一般使用exists代替in()来获取更高的效率

另外对于关联子查询的效率不如使用左外链接(left out join)效率高,这种观点是不正确的,要具体分析。建议经过具体测试验证。

2) Union的限制

         Union的限制外部的条件没法渗入到内层的查询中,以下sql语句:

(Selectfirst_name,last_name from actor order by last_name) union all (selectfirst_name,

last_name fromcustomer order by last_name) limit 20;

假如actor表有200条记录,customer表有900条记录,那么这条sql是扫描出actor的200条记录和customer的900条记录,而后将这些记录放入临时表时,再从临时表中取出前20条记录。能够经过在两个子查询上分别加上limt来减小临时表的数据,见下:

(Selectfirst_name,last_name from actor order by last_name limit 20) union all (selectfirst_name,

last_name fromcustomer order by last_name limit 20) limit 20;

另外须要注意的是,临时表的顺序不必定是正确的,最好在外部加上一个全局的order by操做。

3) 最大值和最小值计算

      Max()与min()方法,mysql优化的不是太好,好比要查找最小的actor_id,以下sql语句:

Selectmin(actor_id) from film_actor;

Actor_id是主键,可是也会作全表扫描。曲线救国的方式,来改变查询提交效率,就是移除min方法,改后的sql语句以下:

Select actor_idorder by desc limit 1;

可是,这条sql语句已经没法表达它的本意了,可是有时候为了得到更高的查询性能,不得不放弃一些原则。

4) 不能同时在同一个表上执行查询和更新

         以下sql语句:

Update tb1 asouter_tb1 set cnt=(select count(*) from tb1 as inner_tb1 where inner_tb1.type=

Outer_tb1.type);

这条sql语句是没法执行的,不过能够经过生成表的形式来绕过上面的限制,由于mysql会把这个表当成临时表来处理。这实际上执行了两个查询:一个是子查询中的select语句,另外一个是多表关联update,只是关联的表是一个临时表,子查询会在update语句打开表以前就完成,因此下面的sql能正常执行:

Update tb1 innerjoin(select type,count(*) as cnt from tb1 group by type) as der using(type) set

Tb1.cnt=der.cnt;

3. 优化特定类型的查询

      对于特定类型的查询,mysql具备相应的优化机制,当要使用这些特定类型的查询时,遵循优化机制,才能达到性能最佳。下面介绍这些特定类型的查询:

1)  优化count()查询

         Count()函数是用来统计记录行数或某列值的数量,只统计不为null的值,例如count

(name),只统计name列不为null的个数,若name列都不是null,那么count(name)等价于

Count(*)。当为count(*)时,是统计全部的行数。

         在没有任何where条件的count()函数,查询速率很是快,由于mysql此时无需计算实际的行数,而是利用存储引擎的特性直接获取这个值。所以带有where条件的查询的行数越多,count()方法效率就会越低,能够经过间接的方式优化此类状况的查询,例以下面的sql语句:

Select count(*)from city where id>5;

当id>5时,该查询须要扫描上万条数据时,而id<=5须要扫描的条数不多时,就能够经过以下的方式提升查询效率:

Select (selectcount(*) from city) –count(*) from city where id<=5;

这样就会大大下降扫描的行数,mysql查询优化阶段会把内部查询结果当成一个常量来处理。

         另外,咱们也可使用count来统计同一列不一样内容的数量,以下sql语句:

Selectcount(color=’blue’ or null) as bluecount,count(color=’red’ or null ) asredcount from city;

         对于不须要精确统计行数时,好比统计某网站的在线的活跃人数,那么就能够利用explain命令来获取近似值。如若要获取精确的行数,记录由很是多时,带有where的统计,效率确定会低,而且使用索引也达不到要求时,这时就须要修改应用架构了,好比使用汇总表或外部缓存等。

2)  优化关联查询

        当使用关联查询时,确保on或using子句中的列上有索引。确保group by和order by表达式只涉及到一个表中的列,这样才能使用索引来优化这个过程。

3) 优化子查询

         优化子查询最重要的建议是尽量的使用关联查询。可是在mysql5.6版本以上不用考虑这个问题。

4) 优化group by和distinct

         Mysql内部优化器会相互转化这两种查询,它们均可以使用索引来来优化,并且索引优化是最优效的方式。使用标识列分组效率会比其它列更高,例以下面sql:

Selectactor.first_name,actor.last_name,count(*) from film_actor inner join actorusing(actor_id)

Group byactor.fist_name,actor.last_name;

改为使用标识列分组,效率会更高,见下面sql:

Selectactor.first_name,actor.last_name,count(*) from film_actor inner join actorusing(actor_id)

Group byactor.actor_id;

须要注意的是这种查询是利用了actor_id与first_name、last_name的直接关系,改写后结果不受影响。另外,这种sql写法在oracle是行不通的,会报ORA-00979错误,所以oracle要求分组列必须包括全部的非组合函数涉及到的列,对于mysql这种写法也不是都是能够是,能够经过将mysql的sql_mode设置为ONLY_FULL_GROUP_BY来禁止这种模式。对于以上的写法有时会形成查询结果不许确,好比有以下数据:

若是想找到每一个class里面的最大的age,则须要使用group by和max。以下的sql语句,则输出结果有错误:

获得的结果,姓名和年龄并不对应,若sql_mode设置为ONLY_FULL_GROUP_BY,上述写法会直接报错的。能够采用下面的写法:

Select id,namefrom test inner join( select max(age),class from test group by class) tusing(class);

或者

Select * from(select * from test order by age desc) as t group by class;

这两种方式,均用到子查询,成本就会有点高,由于子查询会建立临时表。

5) 优化limit分页

      Mysql中使用limit m,n函数分页,其中m表明位置偏移量,n表明获取的行数。在使用limit分页时,一般加上order by子句。若是有对应的索引的,效率通常会不错。可是若位置偏移量很是大时,好比limit 10000,20,那么mysql须要查询100020条记录,只返回最后20条记录,前面10000条记录都被抛弃掉,这样的代价很大,效率会很低。

      优化此类分页查询最简单的方法就是尽量的执行索引覆盖扫描,而不是查询全部的列,而后根据须要作一次关联操做再返回所须要的列。例以下面的例子:

Selectfilm_id,description from film order by title limit 10000,20;

那么查询改写成下面的形式:

Selectfilm_id,description from film inner join(select film_id from film order bytitle limit 10000,20) as A using(film_id).

这样的方式就会大大提升查询效率,让mysql扫描尽量少的页面,获取须要访问的记录后再根据关联列回原表查询全部的列。

      Limit的分页效率问题,主要就是位置偏移量的问题,当位置偏移量很大时,mysql须要扫描大量的行并抛弃掉。若是可使用书签记录上次取数据的位置,那么下次就能够直接从该书签记录的位置开始扫描,而不须要位置偏移量。例如,若须要按租借记录作翻页,那么能够根据最新一条租借记录向后追溯,这种方法可行是由于租借记录的主键是单调增长的。首先使用下面的sql获取第一组结果:

Select * fromrental order by rental_id desc limit 20;

假设上面的返回的的主键16049到16030的记录,那么下一页的查询sql为:

Select * fromrental where rental_id<16030 order by rental_id desc limit 20;

该技术的好处是不管翻到多少页,性能都是好的。

      以上是经过学习《高性能mysql》这本书,根据我的理解,对一些关键的知识作了总结,以便之后翻看学习。俗话说“好脑壳不如烂笔头”,就是这个道理。

      本文只是总结了基础的mysql性能优化的知识,更高级的性能优化,好比分区、分表等,请参考《高性能mysql》、mycat组件学习资料等。