古越剑箫

学习是一种习惯

  :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: :: 管理 ::

Explain性能分析

sql索引使用的规则

 

单表查询优化

建表sql

CREATE TABLE `sys_student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `v_name` varchar(20) DEFAULT '' COMMENT '姓名',
  `age` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '年龄',
  `grade` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '年级',
  `course` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '所学课程',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='学生表';

 

查询,年龄(age)=7,年级(grade)>7年级,所学课程(course)最多的学生

explain select * from sys_student a where a.age=7 and a.grade>7 ORDER BY a.course DESC LIMIT 1;

image

explain分析之后发现,当前sql存在两个问题,1、没有使用到索引ALL全表扫描,2、Using filesort 文件排序

优化1:加索引

ALTER TABLE sys_student ADD INDEX idx_age_grade_course(age,grade,course);

 

image

使用到了age和grade两个索引,type是rang是可以接受,但是extra有Using filesort,grade>7说明之后的course索引无法使用。证明这个sql还不是最优方案

-- 删除之前的索引
DROP INDEX idx_age_grade_course on sys_student;
-- 只对 age和course建索引
ALTER TABLE sys_student ADD INDEX idx_age_course(age,course);

再次执行之前sql,使用了索引,并且没有Using filesort。

image

 

多表关联查询优化

建表sql

CREATE TABLE `sys_student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `v_name` varchar(20) DEFAULT '' COMMENT '姓名',
  `age` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '年龄',
  `grade` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '年级',
  `course` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '所学课程',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='学生表';

INSERT INTO `sys_student` VALUES ('1', '张三', '7', '1', '2', '上海', '2020-03-11 17:36:06');
INSERT INTO `sys_student` VALUES ('2', '李四', '7', '1', '2', '北京', '2020-03-12 14:29:04');
INSERT INTO `sys_student` VALUES ('3', '王五', '6', '1', '2', '杭州', '2020-03-12 14:29:25');

CREATE TABLE `sys_subject` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `grade` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '年级',
  `subject` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '科目',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='年级科目表';

INSERT INTO `sys_subject` VALUES ('1', '1', '5');
INSERT INTO `sys_subject` VALUES ('2', '2', '8');

执行sql

EXPLAIN select a.* from sys_student a LEFT JOIN sys_subject b on a.grade=b.grade;

image

根据执行结果分析,发现两个表都是全表扫描,第一行关联rows是3行,第二行关联rows是2行,执行时间就是全表扫描3行*2行,但是如果把这个数据放大1万倍呢?

 

-- 在sys_student上建立索引
ALTER TABLE sys_student ADD INDEX idx_grade(grade);

再次执行sql,发现没有任何改变

image

 

-- 删除sys_student上建立的索引,在sys_subject上重新建立索引
DROP INDEX idx_grade ON sys_student;
ALTER TABLE sys_subject ADD INDEX idx_grade(grade);

image

这次执行结果,第二行type已经是ref级别,使用到索引,且Extra显示Using index了,实际执行时间=3行*1行

综上,使用left join查询时 (left join 左边是驱动表,右边是被驱动表)

1、左边数(驱动表)会被全表扫描,右边表(被驱动表,join后面的表)关联字段加索引会提高查询效率。

2、left join,选择小表作为驱动表,大表作为被驱动表

3、inner join时,mysql会自动把小结果集的表宣威驱动表

4、子查询尽量不要放在被驱动表,有可能使用不到索引。如果遇到子查询作为被驱动表可以使用两个left优化若必须用到子查询可以将子查询作为驱动表,因为驱动表是全表扫描,type肯定是all。

 

order by 优化

mysql支持二种方式的排序,FileSort和Index。Index效率高,它指MySQL扫描索引本身完成排序。FileSort(文件排序)方式效率较低。

 

建表sql

CREATE TABLE `sys_student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `v_name` varchar(20) DEFAULT '' COMMENT '姓名',
  `age` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '年龄',
  `grade` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '年级',
  `course` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '所学课程',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='学生表';

执行select查询,发现没有索引且使用Using filesort文件排序

image

 

-- age建立单个索引
ALTER TABLE sys_student ADD INDEX idx_age(age);

依旧执行之前的sql

image

why?没有索引+文件排序,继续优化,尝试使用覆盖索引

image


image


image


image

因此,在没有where条件检索的情况下,必满足覆盖索引,才会使用索引排序

-- 删除之前idx_age索引,建立idx_age_grade联合索引
DROP INDEX idx_age ON sys_student ;
ALTER TABLE sys_student add INDEX idx_age_grade(age,grade);

 

image


image


image


image


image

 

order by 满足两种情况才会使用索引,1,order by 语句遵循索引最左原则,2,使用where子句与order by子句条件列组合满足索引最左原则

filesort(文件排序)的两种算法,双路排序和单路排序,请看这里

 

limit 分页优化

表数据269万+条数据

image

取出第1000000页的10条数据,

 

--sql1写法
select * from tbinvestjxbankrecord a ORDER BY id ASC LIMIT 1000000,10;

image


--sql2写法
SELECT * FROM tbinvestjxbankrecord WHERE id >= (SELECT id FROM tbinvestjxbankrecord  ORDER BY id ASC  LIMIT 1000000, 1) LIMIT 10;

image


--sql3写法
select * from tbinvestjxbankrecord INNER JOIN 
( select id from tbinvestjxbankrecord ORDER BY id ASC LIMIT 1000000,10) a on a.id=tbinvestjxbankrecord.id;

image

sql2和sql3比sql1要减少将近1倍的时间

这里尤其要注意 select * 和 select id ,得到的id不一致的问题,造成这个问题的主要原因之一是filesort 算法的问题,解决的办法是必须要order by

group by 优化

group by的本质的是先排序再分组

-- 建表sql
CREATE TABLE `sys_student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `v_name` varchar(20) DEFAULT '' COMMENT '姓名',
  `age` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '年龄',
  `grade` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '年级',
  `course` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '所学课程',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='学生表';

 

image


image


image
image

 

总结来说,group by 主要问题是临时表和文件排序,

1、如果group by的字段无索引,产生临时表和文件排序

2、如果group by 的字段有索引,order by 的字段无索引,产生临时表和文件排序

3、如果group by 和 order by 的索引字段不是同一个,产生临时表和文件排序

4、如果group by和order by的索引不遵循最左原则,产生临时表和文件排序

 

关于sql查询优化本文主要是在应用层进行浅析,至于mysql底层的原理,后续有时间会单独写。

posted on 2020-03-11 17:53  古越剑箫  阅读(243)  评论(0编辑  收藏  举报