MySQL查询性能优化

工作中产生的慢sql查询,大部分都是由于表中索引建立不当以及sql中没有充分的利用到索引,导致在查询性能上差强人意。那么我们该如何合理的建立索引和使用到索引呢?

在这之前 我们先来了解下索引的弊端和优势以及分类

索引的弊端:

         1.索引本身很大, 可以存放在内存/硬盘(通常为 硬盘)

         2.索引不是所有情况均适用: a.少量数据  b.频繁更新的字段   c.很少使用的字段

         3.索引会降低增删改的效率(增删改  查)

   优势:1提高查询效率(降低IO使用率)

            2.降低CPU使用率 (...order by age desc,因为 B树索引 本身就是一个 好排序的结构,因此在排序时  可以直接使用)

分类:

     主键索引:  不能重复。id    不能是null

     唯一索引  :值不能重复。id    可以是null

     单值索引  : 单列, age ;一个表可以多个单值索引,name。

     联合索引  :多个列构成的索引 (相当于 二级目录 :  z: zhao)  (name,age)   (a,b,c,d,...,n)

如果对于索引结构不太了解的可以看这篇文章:

Mysql索引结构_chenxuezhou的博客-CSDN博客

一、索引的建立

1、代码先行,索引后上

等到主体业务功能开发完毕,把涉及到该表相关sql都要拿出来分析之后再建立 索引,不要是建完表马上就建立索引

2、联合索引尽量覆盖条件

比如可以设计一个或者两三个联合索引(尽量少建单值索引),让每一个联合索引都尽量去包含sql语句里的 where、order by、group by的字段,还要确保这些联合索引的字段顺序尽量满足sql查询的最左前缀原 则。

3、长字符串我们可以采用前缀索引

尽量对字段类型较小的列设计索引,比如说什么tinyint之类的,因为字段类型较小的话,占用磁盘空间也会 比较小。但是很多时候你就是要针对varchar(255)这种字段建立 索引,可以稍微优化下,比如针对这个字段的前20个 字符建立索引,就是说,对这个字段里的每个值的前20个字符放在索引树里,如果业务不允许,也只能对整个字段加索引

4、不要在小基数字段上建立索引

索引基数是指这个字段在表里总共有多少个不同的值,比如一张表总共300万行记录,其中有个性别字段, 其值不是男就是女,那么该字段的基数就是2,所以此时加索引就显得毫无意义。

5、where与order by冲突时优先where

般这种时候往往都是让where条件去使用索引来快速筛选出来一部分指定的数据,接着再进行排序。 因为大多数情况基于索引进行where筛选往往可以最快速度筛选出你要的少部分数据,然后做排序的成本可 能会小很多。

6不要将索引建立在含有NULL值的字段上

只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为NULL

二、索引优化

下面是一个联合索引 由name、age、positon组成

MySQL查询性能优化_第1张图片

 

1、mysql在使用不等于(!=或者<>),not in ,not exists 的时候无法使用索引会导致全表扫描
< 小于、 > 大于、 <=、>= 这些,mysql内部优化器会根据检索比例、表大小等多个因素整体评估是否使用索引。当通过这些条件去检索和通过我全表扫描去检索的数据量差不多的时候 mysql一般会不走索引 因为查询的字段如果没有被索引覆盖还会再去回表 

2、is null,is not null 一般情况下也无法使用索引

3、like以通配符开头('$abc...')mysql索引失效会变成全表扫描操作

问题:解决like'%字符串%'索引不被使用的方法?
使用覆盖索引,查询字段必须是建立覆盖索引字段
1 EXPLAIN SELECT name,age,position FROM employees WHERE name like '%Lei%';

4、用or或in,用它查询时,mysql不一定使用索引,mysql内部优化器会根据检索比例、表大小等多个因素整体评估是否使用索引

可以使用联合(UNION)、between进行优化

5、索引列上做任何操作(计算、函数、(自动or手动)类型转换(字符串不加单引号)),会导致索引失效而转向全表扫描

6、存储引擎不能使用联合索引中范围条件右边的列 

EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 22 AND position ='manager';

只会用到name,age 列 因为当age为范围查询 position就不一定是有序的了 也就利用不到索引

联合索引第一个字段用范围不会走索引

 EXPLAIN SELECT * FROM employees WHERE name > 'LiLei' AND age = 22 AND position ='manager';

 in和or在表数据量比较大的情况会走索引,在表记录不多的情况下会选择全表扫描
 EXPLAIN SELECT * FROM employees WHERE name in ('LiLei','HanMeimei','Lucy') AND age = 22 AND position='manager';

 like KK% 一般情况都会走索引
 EXPLAIN SELECT * FROM employees WHERE name like 'LiLei%' AND age = 22 AND position ='manager';

 like KK%相当于=常量,%KK和%KK% 相当于范围

分页查询优化:

很多时候我们业务系统实现分页功能可能会用如下sql实现
 select * from employees limit 90000,5;
表示从表  employees  中取出从 90001 行开始的 5 行记录。看似只查询了 5 条记录,实际这条 SQL 是先读取 90005条记录,然后抛弃前 90000 条记录,然后读到后面 5 条想要的数据。因此要查询一张大表比较靠后的数据,执行效率是非常低的。

1、如果对于是主键自增且连续没有间断的数据

因为主键是自增并且连续的,所以可以改写成按照主键去查询从第 90001开始的五行数据,如下:
select * from employees where id > 90000 limit 5;

通过这种手段可以大大减少扫描行数 但是争对这种优化其实实际业务场景很少使用

2、根据非主键字段排序的分页查询

我们可以让排序时返回的字段尽可能少,让排序和分页操作先查出主键,然后根据主键查到对应的记录,改写如下:

 select * from employees e inner join (select id from employees order by name limit 90000,5) ed
on e.id = ed.id;

Join关联查询优化:

在优化之前我们先来了解一下join常见的的两种算法(Nested-Loop Join 算法,Block Nested-Loop Join 算法)
一、 嵌套循环连接 Nested-Loop Join(NLJ) 算法
一次一行循环地从第一张表(称为驱动表)中读取行,在这行数据中取到关联字段,根据关联字段在另一张表(被驱动表)里取出满足条件的行,然后取出两张表的结果合集。

例如t1表有10000条数据  t2有100条数据
 select * from t1 inner join t2 on t1.a= t2.a;

上面sql的大致流程如下:
1. 从表 t2 中读取一行数据(如果t2表有查询过滤条件的,会从过滤结果里取出一行数据);
2. 从第 1 步的数据中,取出关联字段 a,到表 t1 中查找;
3. 取出表 t1 中满足条件的行,跟 t2 中获取到的结果合并,作为结果返回给客户端;
4. 重复上面 3 步。
整个过程会读取 t2 表的所有数据(扫描100行),然后遍历这每行数据中字段 a 的值,根据 t2 表中 a 的值索引扫描 t1 表中的对应行(扫描100次 t1 表的索引,1次扫描可以认为最终只扫描 t1 表一行完整数据,也就是总共 t1 表也扫描了100行)。因此整个过程扫描了 200 行。
如果被驱动表的关联字段没索引,使用NLJ算法性能会比较低(下面有详细解释),mysql会选择Block Nested-Loop Join算法

二、 基于块的嵌套循环连接 Block Nested-Loop Join( BNL )算法
把驱动表的数据读入到 join_buffer 中,然后扫描被驱动表,把被驱动表每一行取出来跟 join_buffer 中的数据做对比。

上面sql的大致流程如下:
1. 把 t2 的所有数据放入到 join_buffer 中
2. 把表 t1 中每一行取出来,跟 join_buffer 中的数据做对比
3. 返回满足 join 条件的数据
整个过程对表 t1 和 t2 都做了一次全表扫描,因此扫描的总行数为10000(表 t1 的数据总量) + 100(表 t2 的数据总量) =
10100。并且 join_buffer 里的数据是无序的,因此对表 t1 中的每一行,都要做 100 次判断,所以内存中的判断次数是
100 * 10000= 100 万次。
这个例子里表 t2 才 100 行,要是表 t2 是一个大表,join_buffer 放不下怎么办呢?
join_buffer 的大小是由参数 join_buffer_size 设定的,默认值是 256k。如果放不下表 t2 的所有数据话,策略很简单,就是分段放。
比如 t2 表有1000行记录, join_buffer 一次只能放800行数据,那么执行过程就是往 join_buffer 里放800行记录,然后从 t1 表里取数据跟 join_buffer 中数据对比得到部分结果,然空  join_buffer ,再放入 t2 表剩余200行记录,再次从 t1 表里取数据跟 join_buffer 中数据对比。所以就多扫了一次 t1 表。
被驱动表的关联字段没索引为什么要选择使用 BNL 算法而不使用 Nested-Loop Join 呢?
如果上面第二条sql使用 Nested-Loop Join,那么扫描行数为 100 * 10000 = 100万次,这个是磁盘扫描。
很显然,用BNL磁盘扫描次数少很多,相比于磁盘扫描,BNL的内存计算会快得多。
因此MySQL对于被驱动表的关联字段没索引的关联查询,一般都会使用 BNL 算法。如果有索引一般选择 NLJ 算法,有索引的情况下 NLJ 算法比 BNL算法性能更高

对于关联sql的优化:
关联字段加索引,让mysql做join操作时尽量选择NLJ算法
小表驱动大表,写多表连接sql时如果明确知道哪张表是小表可以用straight_join写法固定连接驱动方式,省去mysql优化器自己判断的时间

对于小表定义的明确:
在决定哪个表做驱动表的时候,应该是两个表按照各自的条件过滤,过滤完成之后,计算与 join 的各个字段的总数据量,数据量小的那个表,就是“小表”,应该作为驱动表。

in和exsits优化:


原则:小表驱动大表,即小的数据集驱动大的数据集
in:当B表的数据集小于A表的数据集时,in优于exists
 select * from A where id in (select id from B)

exists:当A表的数据集小于B表的数据集时,exists优于in

select * from A where exists (select 1 from B where B.id = A.id)

排序优化:

1、MySQL支持两种方式的排序filesort和index,Using index是指MySQL扫描索引本身完成排序。index
效率高,filesort效率低。
2、order by满足两种情况会使用Using index。
1) order by语句使用索引最左前列。
2) 使用where子句与order by子句条件列组合满足索引最左前列。
3、尽量在索引列上完成排序,遵循索引建立(索引创建的顺序)时的最左前缀法则。
4、如果order by的条件不在索引列上,就会产生Using filesort。
5、能用覆盖索引尽量用覆盖索引
6、group by与order by很类似,其实质是先排序后分组,遵照索引创建顺序的最左前缀法则。对于group
by的优化如果不需要排序的可以加上order by null禁止排序。注意,where高于having,能写在where中
的限定条件就不要去having限定了。

你可能感兴趣的:(mysql,b树,数据库,索引,mysql优化)