mysql优化

184 阅读7分钟

conut(*)与count(n)区别:

  • 1. 比较count(*)和count(字段名)的区别:前者对行的数目进行计算,包含null,后者对特定的列的值具有的行数进行计算,不包含null,得到的结果将是除去值为null和重复数据后的结果。

  • 2. count(1)跟count(主键)一样,只扫描主键

  • 3. count(*)和count(主键)使用方式一样,但在性能上有略微区别,mysql对前者做了优化。 count(主键)不一定比count(其余索引快)。

  • 4. count(*)的执行效率比count(col)高,因此可用count(*)时就不要去用count(col)。

  • 5. 在不加WHERE限制条件的情况下,COUNT(*)与COUNT(COL)基本可认为是等价的; 但是在有WHERE限制条件的情况下,COUNT(*)会比COUNT(COL)快非常多;

常用的MySQL优化方法,共19条,具体如下:

一、EXPLAIN 使用执行计划

做MySQL优化,我们要善用EXPLAIN查看SQL执行计划。

  • id列:是查询的顺序号,有几个 select 就显示几行,id的顺序是按 select 出现的顺序增长的。id列的值越大执行优先级越高越先执行,

  • select_type列:表明查询的类型:

    1)simple:表明当前行对应的select是简单查询,不包含子查询和union

    2)primary:表明当前行对应的select是复杂查询中最外层的 select

    3)subquery:表明当前行对应的select是包含在 select 中的子查询(不在 from 子句中)

    4)derived:表明当前行对应的select是包含在 from 子句中的子查询。

  • table列:table列的结果表明当前行对应的select正在访问哪个表。

  • type列**,访问类型(**连接类型)。也就是优化器决定怎么查找数据表中的行,以及查找数据行记录的大概范围。该列的取值优化程度的优劣,从最优到最差依次为:null>system(表中数据只有一条匹配时为system)> const(最多有一个匹配行读取1次) > eq_ref > ref > range (范围查询)> index (扫描全表索引)> ALL(全表扫描)。一般来说,要保证查询达到range级别,最好达到ref。

  • possible_keys列这一列的结果表明查询可能使用到哪些索引。

  • **key列,**使用到的索引名。如果没有选择索引,值是NULL。可以采取强制索引方式。

  • **key_len列,**索引长度。表明了在索引里使用的字节数,通过这个值可以大致估算出具体使用了联合索引中的前几个列

  • ref列:表明了在key列记录的索引中,表查找值所用到的列或常量,常见的有:const(常量),字段名,如user.user_id

  • **rows列,**扫描行数。该值是个预估值。

  • **extra列,**详细说明。注意,常见的不太友好的值,如下:Using filesort,Using temporary。

二、sql优化

1、SELECT语句务必指明字段名称

2、当只需要一条数据的时候,使用limit 1

3、如果排序字段没有用到索引,就尽量少排序

**4、对于联合索引来说,要遵守最左前缀法则
**

5、尽量用union all代替union

union和union all的差异主要是前者需要将结果集合并后再进行唯一性过滤操作,这就会涉及到排序,增加大量的CPU运算,加大资源消耗及延迟。当然,union all的前提条件是两个结果集没有重复数据。

6、区分in和exists、not in和not exists

select * from 表A where id in (select id from 表B)

相当于   select * from 表A where exists(select * from 表B where 表B.id=表A.id)

区分in和exists主要是造成了驱动顺序的改变(这是性能变化的关键),如果是exists,那么以外层表为驱动表,先被访问,如果是IN,那么先执行子查询。所以IN适合于外表大而内表小的情况;EXISTS适合于外表小而内表大的情况。

关于not in和not exists,推荐使用not exists,不仅仅是效率问题,not in可能存在逻辑问题。如何高效的写出一个替代not exists的SQL语句?

原SQL:select colname … from A表 where a.id not in (select b.id from B表)

高SQL:select colname … from A表 Left join B表 on where a.id = b.id where b.id is null

7、SQL语句中IN包含的值不应过多

MySQL对于IN做了相应的优化,即将IN中的常量全部存储在一个数组里面,而且这个数组是排好序的。但是如果数值较多,产生的消耗也是比较大的。再例如:select id from t where num in(1,2,3) 对于连续的数值,能用between就不要用in了;再或者使用连接来替换。

8、使用合理的分页方式以提高分页的效率

select id,name from product limit 866613, 20

使用上述SQL语句做分页的时候,随着表数据量的增加,直接使用limit分页查询会越来越慢。

优化的方法如下:可取前一页的最大行数的id,然后根据这个最大的id来限制下一页的起点。SQL可以采用如下的写法:

select id,name from product where id> 866612 limit 20

9、分段查询

在一些用户选择页面中,可能一些用户选择的时间范围过大,造成查询缓慢。主要的原因是扫描行数过多。这个时候可以通过程序,分段进行查询,循环遍历,将结果合并处理进行展示。

三、索引注意思事项

**1、应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,**如果限制条件中其他字段没有索引,尽量少用or;

如果条件中有or(并且其中有or的条件是不带索引的),即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因)。注意:要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引

应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num=10 or num=20
可以这样查询:
select id from t where num=10
union all
select id from t where num=20  

2、尽量避免在where子句中对字段进行null值判断

对于null的判断会导致引擎放弃使用索引而进行全表扫描。

3、不建议使用%前缀模糊查询

例如LIKE“%name”或者LIKE“%name%”,这种查询会导致索引失效而进行全表扫描。但是可以使用LIKE “name%”。

那如何查询%name%?答案:使用全文索引。

4、尽量避免在where子句中对字段进行表达式操作

比如:select user_id,user_project from user_base where age*2=36;

中对字段就行了算术运算,这会造成引擎放弃使用索引,建议改成:

select user_id,user_project from user_base where age=36/2;

5、尽量避免范围查找,注意范围查询语句

对于联合索引来说,如果存在范围查询,比如between、>、<等条件时,会造成后面的索引字段失效。

四、不常用sql优化

1、必要时可以使用force index来强制查询走某个索引

有的时候MySQL优化器采取它认为合适的索引来检索SQL语句,但是可能它所采用的索引并不是我们想要的。这时就可以采用forceindex来强制优化器使用我们制定的索引。

2、不使用ORDER BY RAND()

select id from `dynamic` order by rand() limit 1000;

3、尽量避免隐式类型转换

where子句中出现column字段的类型和传入的参数类型不一致的时候发生的类型转换,建议先确定where中的参数类型。

4、关于JOIN优化

join的实现原理: join的实现是采用Nested Loop Join算法,就是通过驱动表的结果集作为循环基础数据,然后一条一条的通过该结果集中的数据作为过滤条件到下一个表中查询数据,然后合并结果。如果有多个join,则将前面的结果集作为循环数据,再一次作为循环条件到后一个表中查询数据。

join语句的优化

1. 用小结果集驱动大结果集,尽量减少join语句中的Nested Loop的循环总次数;

2. 优先优化Nested Loop的内层循环,因为内层循环是循环中执行次数最多的,每次循环提升很小的性能都能在整个循环中提升很大的性能;

3. 对被驱动表的join字段上建立索引;

4. 当被驱动表的join字段上无法建立索引的时候,设置足够的Join Buffer Size。

1)MySQL中没有full join,可以用以下方式来解决:

select * from A left join B on B.name = A.namewhere B.name is nullunion allselect * from B;

2)尽量使用inner join,避免left join:

参与联合查询的表至少为2张表,一般都存在大小之分。如果连接方式是inner join,在没有其他过滤条件的情况下MySQL会自动选择小表作为驱动表,但是left join在驱动表的选择上遵循的是左边驱动右边的原则,即left join左边的表名为驱动表。

3)合理利用索引:

被驱动表的索引字段作为on的限制字段。

4)利用小表去驱动大表:

如果能够减少驱动表的话,减少嵌套循环中的循环次数,以减少 IO总量及CPU运算的次数。

5)巧用STRAIGHT_JOIN:

inner join是由MySQL选择驱动表,但是有些特殊情况需要选择另个表作为驱动表,比如有group by、order by等「Using filesort」、「Using temporary」时。STRAIGHT_JOIN来强制连接顺序,在STRAIGHT_JOIN左边的表名就是驱动表,右边则是被驱动表。在使用STRAIGHT_JOIN有个前提条件是该查询是内连接,也就是inner join。其他链接不推荐使用STRAIGHT_JOIN,否则可能造成查询结果不准确。

来源:zhuanlan.zhihu.com/p/49888088