Mysql

关注公众号 jb51net

关闭
首页 > 数据库 > Mysql > sql优化实践

Mysql中的sql优化实践详解

作者:这是一条海鱼

这篇文章主要介绍了Mysql中的sql优化实践详解,SQL优化是指通过改进SQL语句的编写和查询计划的设计来提高数据库查询性能的过程,在优化SQL时,可以采取一些措施来提升查询性能,需要的朋友可以参考下

1、order by优化

MySql中有2种排序方式Using filesort和Using index

Using index:通过有序索引顺序扫描直接返回有序数据,这种情况即为using index,不需要额外排序,操作效率高。

Using filesort:通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区sort bufferr中完成排序操作。它低层分为单路排序和双路排序

create index n_a_p_index on person (name, age, phone);

根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则。

explain select name from person order by age

在这里插入图片描述

注意:判断order by性能,我们主要根据Extra中的只来进行判断,虽然key显示我们使用了索引,但是他是因为我们查询时使用了索引,而在Extra中,出现Using filesort,这说明我们排序没有使用到索引。

优化如下

explain select name from person order by name,age

在这里插入图片描述

尽量使用覆盖索引。

explain select * from person order by name,age

在这里插入图片描述

多字段排序,一个升序一个降序,此时需要注意联合索引在创建时的规则(ASC/DESC)。

explain select name,age from person order by name,age desc

在这里插入图片描述

重新创建索引

create index n_a_p_index on person (name asc , age DESC , phone asc);
explain select name,age from person order by name,age desc

在这里插入图片描述

如果不可避免的出现filesort,大数据量排序时,可以适当增大排序缓冲区大小sort buffer size(默认256k)。

2、group by优化

在分组操作时,可以通过索引来提高效率。分组操作时,索引的使用也是满足最左前缀法则的。

create index n_a_p_index on person (name, age, phone);
explain select age ,count(*) from person group by age

在这里插入图片描述

Using temporary,可以看到group by时,使用到了临时表,性能不好,可以做如下优化

explain select age ,count(*) from person where name = '小明' group by age

在这里插入图片描述

3、limit 优化

一个常见又非常头疼的问题就是limit2000000,10,此时需要MySQL排序前2000010记录,仅仅返回2000000-2000010 的记录,其他记录丢弃,查询排序的代价非常大。

select * from person limit 10000,10;

这段sql可以优化成这样

 select * from person p
 inner join(select id from person limit 10000,10) temp on temp.id = p.id

4、count(*)优化

EXPLAIN select count(1) from employees;
EXPLAIN select count(id) from employees; 
EXPLAIN select count(name) from employees; 
EXPLAIN select count(*) from employees;

注意:根据某个字段count不会统计字段为null值的数据行

四个sql的执行计划一样,说明这四个sql执行效率应该差不多

count(1)跟count(字段)执行过程类似,不过count(1)不需要取出字段统计,就用常量1做统计,count(字段)还需要取出 字段,所以理论上count(1)比count(字段)会快一点。

count() 是例外,mysql并不会把全部字段取出来,而是专门做了优化,不取值,按行累加,效率很高,所以不需要用 count(列名)或count(常量)来替代 count()。 为什么对于count(id),mysql最终选择辅助索引而不是主键聚集索引?因为二级索引相对主键索引存储数据更少,检索 性能应该更高,mysql内部做了点优化(应该是在5.7版本才优化)。

常见优化方法

1、查询mysql自己维护的总行数 对于myisam存储引擎的表做不带where条件的count查询性能是很高的,因为myisam存储引擎的表的总行数会被 mysql存储在磁盘上,查询不需要计算 对于innodb存储引擎的表mysql不会存储表的总记录行数(因为有MVCC机制,后面会讲),查询count需要实时计算

2、show table status 如果只需要知道表总行数的估计值可以用如下sql查询,性能很高

show table status like 'person'

在这里插入图片描述

3、将总数维护到Redis里 插入或删除表数据行的时候同时维护redis里的表总行数key的计数值(用incr或decr命令),但是这种方式可能不准,很难 保证表操作和redis操作的事务一致性

4、增加数据库计数表 插入或删除表数据行的时候同时维护计数表,让他们在同一个事务里操作

5、Join关联优化

mysql的表关联常见有两种算法 :Nested-Loop Join和 Block Nested-Loop Join 算法

嵌套循环连接

Nested-Loop Join(NLJ) 算法 一次一行循环地从第一张表(称为驱动表)中读取行,在这行数据中取到关联字段,根据关联字段在另一张表(被驱动 表)里取出满足条件的行,然后取出两张表的结果合集。

 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 中的数据做对比。

select * from t1 inner join t2 on t1.b= t2.b;

在这里插入图片描述Extra 中 的Using join buffer (Block Nested Loop)说明该关联查询使用的是 BNL 算法。 上面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的优化

straight_join解释:straight_join功能同join类似,但能让左边的表来驱动右边的表,能改表优化器对于联表查询的执 行顺序。

比如:select * from t2 straight_join t1 on t2.a = t1.a; 代表指定mysql选着 t2 表作为驱动表。

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

到此这篇关于Mysql中的sql优化实践详解的文章就介绍到这了,更多相关sql优化实践内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文