美文网首页
sql优化案例

sql优化案例

作者: 横渡 | 来源:发表于2019-05-31 14:48 被阅读0次

说明一下sql的优化过程

场景介绍

数据库 mysql 5.6
场景说明

课程表:

DROP TABLE IF EXISTS `course`;
CREATE TABLE `course` (
  `c_id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`c_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

课程表插入数据,20条:

insert into `forapp`.`course` (name) values ('语文'),('数学'),('英语'), ('物理'), ('化学'), ('生物'), ('机械'), ('电子技术'), ('芯片工艺'),('芯片装备制造'),('园艺'), ('哲学'), ('美文赏析'), ('莎士比亚戏剧'), ('量子力学'), ('微积分'), ('高等数学'), ('线性代数'), ('人工智能'), ('统计学');

学生表:

CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=69360 DEFAULT CHARSET=utf8;

学生表数据,7.1万条:

DROP PROCEDURE if EXISTS student_add;
create PROCEDURE student_add(num int)

BEGIN
    DECLARE i int DEFAULT 0;
    START TRANSACTION;
    while i < num DO

    INSERT INTO `forapp`.`student`(name) VALUES (CONCAT('ycy_',i));
    SET i = i + 1;
    end WHILE;
    COMMIT;
END;

CALL student_add(71000);

学生成绩表sc

DROP TABLE IF EXISTS `sc`;
CREATE TABLE `sc` (
  `sc_id` int(11) NOT NULL AUTO_INCREMENT,
  `s_id` int(11) DEFAULT NULL,
  `c_id` int(11) DEFAULT NULL,
  `score` int(11) DEFAULT NULL,
  PRIMARY KEY (`sc_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

成绩表数据,78万条:
学生ID 分布: 1-71000 之间 (CEIL(RAND()71000)
课程ID分布:1-20之间 CEIL(RAND()
20)
考生分数分布: 60-150 之间 FLOOR(60 + RAND()*91)

drop PROCEDURE IF EXISTS sc_add;
CREATE PROCEDURE sc_add(num int)

BEGIN
    DECLARE i int DEFAULT 0;
    START TRANSACTION;
    WHILE i < num DO
    
    INSERT INTO `forapp`.`sc` (s_id, c_id, score) VALUES
    (CEIL(RAND()*71000), CEIL(RAND()*20), FLOOR(60 + RAND()*91));
    set i = i+1;
    END WHILE;
    COMMIT;
END;
CALL sc_add(780000);

查询目的:查找语文考100分的学生。
查询语句

select s.id, s.name from student as s where s.id in (select s_id from sc where c_id = 1 and score = 100)

执行时间:0.85s ~ 1.2s之间,属于比较慢的sql了。

我们来查看下查询计划:

EXPLAIN
select s.id, s.name from student as s where s.id in (select s_id from sc where c_id = 1 and score = 100)

结果如下


explain分析计划.PNG

type类型介绍

现在我们来说明一下结果里的type类型。
type类型取值如下(自左向右,sql性能从最差到最好):

|All | index | range | ref | eq_ref | const, system | null |

All(全表扫描)
MYSQL从头到尾扫描整张表查找行。
index(索引)
根据索引来读取数据,如果索引已经包含了查询数据,只需要扫描索引树,否则执行全表扫描和All类似。
range(范围)
以范围的形式扫描索引,如where条件中使用>,<,>=,<=,in。
ref(引用)
非唯一索引性访问。
eq_ref(等值引用)
使用有唯一性索引查找(主键或唯一性索引)。
const(常量连接)
在整个查询过程中,这个表最多只会有一条匹配的行,比如主键id=1就肯定只有一行。只需要读取一次表数据便能取得所有结果,且表数据在分解执行计划时读取。

Extra 列介绍

Extra 名如其意,表示附加信息。效率由高到低的取值为:

| Using index | Using where | Using filesort | Using temporary|

Using index
表明使用索引,如果只有 Using index ,说明他没有查询到数据表,只用索引表就完成了查询,这个叫覆盖索引。如果同时出现 Using where,代表使用索引来查询记录,也是可以用到索引的,但是需要查询数据表。

Using where
表示条件查询,如果不读取表的所有数据,或不是仅仅通过索引就可以获取所有需要的数据,就会出现 Using where。如果 type 是 All 或者 index,而没有出现该信息,则有可能执行了错误的查询:即返回了所有数据。

**Using filesort **
filesort 是MYSQL所实现的一种排序策略,通常在使用排序语句ORDER BY的时候,会出现该信息。

Using temporary
表示为了得到结果,使用了临时表,这通常是出现在多表联合查询,结果排序的场合。

如果EXPLAIN出现后面两个信息(Using filesort,Using temporary),而rows又比较大,通常意味着你需要调整查询语句,或者需要添加索引,总之要尽量消除这两个消息。

优化分析

接着前面的“场景介绍”。
看EXPLAIN的结果,我们发现对sc表进行查询时type类型为ALL,首先想到创建索引。

给sc表c_id字段和score字段创建一个联合索引。

create index sc_cid_score_index on sc(c_id, score)

优化后执行时间为0.062s - 0.102s。
还能继续优化吗?因为使用了子查询,还有进一步优化的空间。
下面是创建联合索引后的执行计划:


c_id和score添加联合索引后执行计划.PNG

我们通过 extended 查看查询计划内部过程(在命令行执行):

EXPLAIN EXTENDED
select s.id, s.name from student as s where s.id in (select s_id from sc where c_id = 1 and score = 100);

使用show warnings 查看MYSQL是如何优化的:


sql优化.PNG

MYSQL建议使用join。

按照直观的理解,mysql应该先执行子查询:

select s_id from sc where c_id = 1 and score = 100

耗时0.01s速度很快
然后再执行外层查询:

 select s.id, s.name from student as s where s.id in (39129,13767,62895,15638);

0.001s

如果按这种顺序执行速度就很快了。而Mysql是先执行外层查询,再执行内层查询,然后将sql优化成了exists子句,这样就要循环 71000 * 451 次。

那么改用连接查询呢?

SELECT distinct s.* FROM student s 
INNER JOIN sc on sc.s_id = s.id
where sc.c_id = 1 and sc.score = 100;

这时我们发现已经又快了,0.01s。通过explain执行查询计划,发现type类型是 ref和eq_ref。
我们为了分析连接查询的情况,将上一步创建的索引删除。

 drop index sc_cid_score_index on sc;

执行连接语句,结果耗时1.28s。


Join的Explain分析.PNG

猜想是不是在s_id建立个索引。

create index sc_sid_index on sc(s_id);
show index from sc;

耗时3.5s,时间竟然长了!是什么原因?查看查询计划。

貌似先做的连接查询,再进行的where条件过滤。

Note  | 1003 | /* select#1 */ select `forapp`.`s`.`id` AS `id`,`forapp`.`s`.`name` AS `name` from `forapp`.`student` `s` join `forapp`.`sc` where ((`forapp`.`sc`.`s_id` = `forapp`.`s`.`id`) and (`forapp`.`sc`.`score` = 100) and (`forapp`.`sc`.`c_id` = 1))

回到前面的执行计划


join查询计划2.PNG

这里是先做的where条过滤,再做连表,执行计划还不是固定的,那么我们先看下标准的sql执行顺序:

(8) SELECT (9) DISTINCT<select list>
(1) FROM <left_table>
(3) <join_type> JOIN <right_table>
(2)                      ON <join_condition>
(4) WHERE <where_condition>
(5) GROUP BY <group_by_list>
(6) WITH {CUBE|ROLLUP}
(7) HAVING <having_condition>
(10) ORDER BY<order_by_list>
(11) LIMIT <limit_number>

正常情况下是先join然后再进行where过滤,但是我们这里的情况,如果先join,将会有78w条数据发送join操作,因此先执行where过滤是明智的选择。

现在为了排除mysql的查询优化,我自己写一条优化后的sql

select distinct  s.* from (select * from sc where sc.c_id=1 and sc.score=100) t inner join student s on t.s_id = s.id;

1.19s,和没有建s_id索引的时间差不多,查看执行计划:


join分析3.PNG

先提取sc再连表,这样的效率高多了,现在的问题是提取sc的时候出现了全表扫描。那么现在可以明确需要建立的相关索引。

create index sc_cid_score_index on sc(c_id, score);

现在就相当快了,0.001s。快了1000多倍!

join分析4.PNG

我们看到,先提取sc,再连表,都用到了索引。

那么再来执行下sql

select distinct s.* from student s 
inner join sc on sc.s_id = s.id
where sc.c_id = 1 and sc.score=100;

耗时 0.001s。
执行计划:


join分析6.PNG

这是mysql进行了查询语句的优化,先执行了where过滤,再执行连接操作,再执行了连接操作,且都用到了索引。

sc表数据更多的情况

我们来造一些数据,使sc表的数据增加到300万,分数更离散。
考生分数分布: 1-200 之间 FLOOR(60 + RAND()*91)

drop PROCEDURE IF EXISTS sc_add;
CREATE PROCEDURE sc_add(num int)

BEGIN
    DECLARE i int DEFAULT 0;
    START TRANSACTION;
    WHILE i < num DO
    
    INSERT INTO `forapp`.`sc` (s_id, c_id, score) VALUES
    (CEIL(RAND()*71000), CEIL(RAND()*20), 
 CEIL(RAND()*200));
    set i = i+1;
    END WHILE;
    COMMIT;
END;

循环调用存储过程,每次插入1万表数据

add_sc(10000)

或者使用java的jdbc造数据,速度更快。
执行sql:

select DISTINCT s.* from student s  
inner join sc on sc.s_id = s.id 
where sc.c_id = 1 and sc.score=100;

耗时 0.03s,有点慢。
查看执行计划:


join分析7.PNG

这里我们看到从sc表中筛选数据时,用到了联合索引 sc_cid_score_index。联合索引的效率要比两个字段分别建索引的效率要高。
根据c_id=1检索的结果是 158565;根据score=100检索的结果是 12269条。如果是两个字段单独建索引,mysql会有一个 intersect操作,即两个索引同时检索的结果再求并集。显然性能会比联合索引低一些。

从另外一个角度看,该表的数据是317w,实际生产中可能会更多,就索引存储而言都是不小的空间开销。随着数据量 的增加,索引就不能全部加载到内存,而是要从磁盘中去读取,这样索引的个数越多,读磁盘的开销就越大。

这里我们还看到使用了临时表--Using temporary。这个是耗时的操作,mysql在使用distinct,ordre by, group by 数据量大的情况下会产生 Using temporary; Using filesort 效果。这个暂时想不到怎么优化,希望了解的朋友能留言告知一下哈。

总结:

  1. mysql嵌套子查询的效率确实比较低,可以将子查询优化成连接查询;
  2. 连接表时,可以先用where条件对表进行过滤,然后做表连接(虽然mysql会对连接语句进行优化);
  3. 建立合适的索引,必要时建立多列联合索引;
  4. 使用distinct,group by,order by 数据量大的情况下容易产生 sing temporary; Using filesort,这是需要进行优化的;
  5. 学会分析sql执行计划,mysql会对sql进行优化,所以分析执行计划很重要。
  6. mysql实际执行查询操作时,不是按照直观的从头到尾来执行的。

索引优化

优先使用联合索引,使用联合索引效率会更高,尤其是在数据量较大,单个列区分度不高的情况下。

最左前缀

多列索引有最左前缀特性,如在a,b,c字段上创建了联合索引:

create index test_a_b_c_index on test(a,b,c);

那么where条件,where a=1where a=1 and b=1where a=1 and b=1 and c=1都能有效利用索引。

索引覆盖

就是查询的列都建立了索引,这样在获取结果集的时候不用再去磁盘获取其它列的数据,直接返回索引数据即可。

排序

在排序字段上建立索引会大幅度提高sql效率,因为消除了Using temporary; Using filesort 现象。

常用的sql调优经验

  1. 列类型尽量定义成数值类型,且长度尽可能短,如主键和外键,类型字段等;
  2. 建立必要的单列索引;
  3. 根据需要建立多列联合索引;
  4. 索引不是越多越好不能滥用;单张表索引数要控制在5个以内,最好不超过3个;
  5. 根据业务场景建立覆盖索引只查询业务所需字段,这样会极大提高查询效率;
  6. where 条件字段上需要建立索引;
  7. 排序字段需要建立索引;
  8. 分组字段上需要建立索引;
  9. where 条件上不要使用运算函数,以免索引失效。

相关文章

  • 详解MySQL之SQL优化(1)

    MySQL学习笔记(6) SQL优化(1) 优化SQL的一般步骤 本文所涉及案例表来自MySQL的案例库sakil...

  • sql优化案例

    说明一下sql的优化过程 场景介绍 数据库 mysql 5.6场景说明 课程表: 课程表插入数据,20条: 学生表...

  • 数栈SQL优化案例:OR条件优化

    本文整理自:袋鼠云技术荟 | SQL优化案例(2):OR条件优化[https://link.zhihu.com/?...

  • 常用sql优化2019-09-27

    sql优化 sql优化.................................................

  • mysql数据库优化

    1. Mysql优化介绍 1.1 sql优化 a. sql优化分析b. 索引优化c. 常用sql优化d. 常用优化...

  • Mysql 优化

    1.Sql优化 1)sql优化分析2)索引优化3)sql语句优化4)一些常用的技巧优化 (正则、函数) 2.优化数...

  • SQL优化案例分析01

    一、问题 在对Mysql数据库查询时,我们经常会对SQL语句进行优化,以此来提高我们的查询效率。有时一个小小的改动...

  • SQL优化案例——in和exist的优化

    涉及知识:in 和 exist的原理问题现象:在应收应付的单据相关业务中,出现了明显的SQL效率问题。SQL执行耗...

  • sql优化的一般策略

    sql 优化的一般策略:索引优化,sql改写,参数优化,优化器 索引优化 以select * from vvsho...

  • Mysql索引优化

    1、单表索引优化 单表索引优化分析 创建表 建表 SQL 表中的测试数据 查询案例 查询category_id为1...

网友评论

      本文标题:sql优化案例

      本文链接:https://www.haomeiwen.com/subject/wdrwtctx.html