(二)索引优化分析

1. 性能下降 SQL 慢

  • 执行时间长
  • 等待时间长

原因:

  • 查询语句写得烂
  • 索引失效(单值、复合)
  • 关联查询太多 join(设计缺陷或不得已的需求)
  • 服务器调优及各个参数设置(缓冲、线程数等)

2. 常见通用的 Join 查询

1)SQL 执行顺序

2)Join 图

(注:mysql 里面不支持 full outer join 查询,所以可以用 union(去重)来拼接SQL 语句。如:select * from a left join b on a.key = b.key union select * from a right join b on a.key = b.key;)

3. 索引简介

1)简介

MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。可以得到索引的本质:索引是数据结构。简单理解为"排好序的快速查找数据结构"。

数据本身之外,数据库还维护着一个满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构的基础上实现高级查找算法,这种数据结构就是索引。

一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以文件形式存储在硬盘上。

我们平时所说的索引,如果没有特别指明,都是指B树(多路搜索树,并不一定是二叉树)结构组织的索引。其中聚集索引,次要索引,覆盖索引,复合索引,前缀索引,唯一索引默认都是使用B+树索引,统称索引。当然,除了B+树这种类型的索引之外,还有哈希索引(hash index)等。

2)优势

类似大学图书馆建书目索引,提高数据检索效率,降低数据库的IO成本;通过索引列对数据进行排序,降低数据排序成本,降低了CPU的消耗。

3)劣势

实际上索引也是一张表,该表保存了主键和索引字段,并指向实体表的记录,所以索引列也是要占用空间的。

虽然索引大大提高了查询速度,同时却会降低更新表的速度,如果对表INSERT,UPDATE和DELETE。因为更新表时,MySQL不仅要不存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息。

索引只是提高效率的一个因素,如果你的 MySQL 有大数据量的表,就需要花时间研究建立优秀的索引,或优化查询语句。

4)MySQL索引分类

  • 单值索引:即一个索引只包含单个列,一个表可以有多个单列索引。
  • 唯一索引:索引列的值必须唯一,但允许有空值。
  • 复合索引:即一个索引包含多个列。

基本语法:

  • 创建: create [unique] index indexName on mytable(columnname(Length));(写单个就是单值索引,写多个就是复合索引)    alter mytable add [unique] index [indexName] on (columnname(length))
  • 删除:drop index [indexName] on mytable;
  • 查看:show index from table_name\G

5)MySQL 索引结构

BTree索引

Btree索引(或Balanced Tree),是一种很普遍的数据库索引结构,oracle默认的索引类型(本文也主要依据oracle来讲)。其特点是定位高效、利用率高、自我平衡,特别适用于高基数字段,定位单条或小范围数据非常高效。理论上,使用Btree在亿条数据与100条数据中定位记录的花销相同。

Btree 的数据结构如下:

②Hash索引

③full-text全文索引

④R-Tree索引

6)哪些情况需要创建索引

  • 主键自动建立唯一索引
  • 频繁作为查询的条件的字段应该创建索引
  • 查询中与其他表关联的字段,外键关系建立索引
  • 频繁更新的字段不适合创建索引:因为每次更新不单单是更新了记录还会更新索引,加重IO负担
  • Where条件里用不到的字段不创建索引
  • 单间/组合索引的选择问题,who?(在高并发下倾向创建组合索引)
  • 查询中排序的字段,排序字段若通过索引去访问将大大提高排序的速度
  • 查询中统计或者分组字段

7)哪些情况不要创建索引

  • 表记录太少
  • 经常增删改的表
  • 数据重复且分布平均的表字段,因此应该只为经常查询和经常排序的数据列建立索引

注意,如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果。

4. 性能分析

1)MySQL Query Optimizer、

2)MySQL常见瓶颈

  • CPU:CPU饱和一般发生在数据装入内存或从磁盘上读取数据时候
  • IO:磁盘 I/O 瓶颈发生在装入数据远大于内存容量时
  • 服务器硬件的性能瓶颈:top,free,iostat 和 vmstat 来查看系统的性能状态

3)Explain

①简介

使用 EXPLAIN 关键字可以模拟优化器执行 SQL 语句,从而知道 MySQL 是如何处理你的 SQL 语句的。分析你的查询语句或是结构的性能瓶颈。

②作用

  • 表的读取顺序
  • 数据读取操作的操作类型
  • 哪些索引可以使用
  • 哪些索引被实际使用
  • 表之间的引用
  • 每张表有多少行被优化器查询

③怎么用

Explain+SQL语句,如:  explain select * from tb_a;

执行计划包含的信息


 

④各个字段解释

a)id:select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序

三种情况

  • id相同,执行顺序由上至下
  • id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
  • id相同不同,同时存在

b)select_type

有哪些:

查询的类型,主要用于区别 普通查询、联合查询、子查询等的复杂查询

  • SIMPLE:简单的 select 查询,查询中不包含子查询或者 UNION。
  • PRIMARY:查询中若包含任何复杂的子部分,最外层查询则被标记为PRIMARY。
  • SUBQUERY:在 SELECT 或者 WHERE 列表中包含了子查询。
  • DERIVED:在 FROM 列表中包含的子查询被标记为 DERIVED(衍生)    MySQL 会递归执行这些子查询,把结果放在临时表里。
  • UNION:若第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在FROM子句的子查询中,外层SELECT将被标记为:DERIVED
  • UNION RESULT:从UNION表获取结果的SELECT。(两个 union 的结果集合并)

c)table:显示这一行的数据是关于哪张表的

d)type

显示查询使用了何种类型,从最好到最差依次是:system > const > eq_ref > ref > range > index > ALL (一般来说,得保证查询至少达到 range 级别,最好能达到 ref)

  • system:表只有一行记录(等于系统表),这是 const 类型的特例,平时不会出现,这个也可以忽略不计
  • const:表示通过索引一次就找到了,const 用于比较 primary key 或者 unique 索引。因为只匹配一行数据,所以很快。如将主键至于where 列表中,MySQL 就能将该查询转换为一个常量
  • eq_ref:唯一性索引,对于每个索引键,表中只有一条记录与之匹配,常见于主键或唯一索引扫描
  • ref:非唯一索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,
  • 它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体
  • range:只检索给定范围的行,使用一个索引来选择行。key 列显示使用了哪个索引。一般就是在你的 where 语句中出现了 between、<、>、in 等的查询。这种范围扫描索引扫描比全表扫描要好,因为他只需要开始索引的某一点,而结束语另一点,不用扫描全部索引
  • index:Full Index Scan,index 与 ALL 区别为 index 类型只遍历索引树。这通常比 ALL 快,因为索引文件通常比数据文件小。(也就是说虽然 all 和 index 都是读全表,但 index 是从索引中读取的,而all是从硬盘中读的)
  • all:FullTable Scan,将遍历全表以找到匹配的行

备注:一般来说,得保证查询只是达到 range 级别,最好达到 ref

e)possible_keys:显示可能应用在这张表中的索引,一个或多个。查询涉及的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。

f)key:实际使用的索引。如果为 null 则没有使用索引。查询中若使用了覆盖索引,则该索引仅出现在 key 列表中。

g)key_len:表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好。

key_len 显示的值为索引最大可能长度,并非实际使用长度,即 key_len 是根据表定义计算而得,不是通过表内检索出的。

h)ref:显示索引那一列被使用了,如果可能的话,是一个常数。那些列或常量被用于查找索引列上的值。

i)rows:根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数。

j)Extra包含不适合在其他列中显示但十分重要的额外信息。

  • Using filesort:说明 MySQL 会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL 中无法利用索引完成排序操作成为“文件排序”。(“九死一生”)
  • Using temporary:使用了临时表保存中间结果,MySQL 在对查询结果排序时使用临时表。常见于排序 order by 和分组查询 group by(“千钧一发”)
  • using index:表示相应的select操作中使用了覆盖索引(Coveing Index),避免访问了表的数据行,效率不错!如果同时出现 using where,表明索引被用来执行索引键值的查找;如果没有同时出现using where,表面索引用来读取数据而非执行查找动作。覆盖索引(Covering Index)(select 的数据列只从索引中就能获得,不必读取数据行,MySQL 可以利用索引返回 select 列表中的字段,二不必根据索引再次读取数据文件,换句话说查询列就要被所建的索引覆盖)(赞!)
  • Using where:表面使用了 where 过滤
  • using join buffer:使用了连接缓存
  • impossible where:where 子句的值总是 false,不能用来获取任何元组
  • select tables optimized away:在没有 GROUP BY 子句的情况下,基于索引优化 MIN/MAX 操作或者。对于 MyISAM 存储引擎优化COUNT(*) 操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。
  • distinct:优化 distinct,在找到第一匹配的元组后即停止找同样值的工作

5. 索引优化

1)索引分析

①单表

案例:

第一次:

结论:很显然,type 是 all,即最坏的情况。Extra 里还出现了 Using filesort,也是最坏的情况。优化是必须的。

第二次,开始优化:新建索引

结论:type 变成了 range,这是可以忍受的。但是 extra 里使用 Using filesort 仍是无法接受的。但是已经建立了索引,为什么没有用呢?这是因为按照 BTree 索引的工作原理,先排序 category_id,如果遇到相同的 category_id 则再排序 comments,如果再遇到相同的 comments 则再排序 views。当 comments 字段在联合索引里处于中间位置时,因 comments > 1 条件时一个范围值(所谓 range),MySQL 无法利用索引再对后面的 views 部分进行检索,即 range 类型查询字段后面的索引无效

第三次,删除第二次的索引,重新建索引,这次结果就理想了:

如图,comments 如果范围精确了,性能就会变好。所以建 sql 语句的时候,尽量用具体的值,别用范围。

②两表

案例:

explain 分析:

第一次,直接左连接查询:

结论:type 都有 all。

第二次,开始加索引,左连接 索引加在右表的情况:

结论:可以看到第二行的 type 变成了 ref,rows 也变成了优化比较明显。这是由左连接特性决定的。left join 条件用于确定如何从右表搜索行,左边一定都有,所以右边是关键点,一定需要建立索引。

第三次,删除第二次的索引,新建左表索引。

结论:效果不如第二次。所以,左连接索引建在右表!

再来看一个右连接查询:

结论:优化比较明显,这是因为 right join 条件用于确定如何从左表搜索行,右边一定都有,所以左边是是我们的关键点,一定要建立索引。

③三表

第一次,直接查询:

第二次,开始建立索引:

结论:后两行的 type 都是 ref 且总 rows 优化很好,效果不错。因此索引最好设置在需要经常查询的字段中。

Join 语句的优化:

  • 尽可能减少 Join 语句中的 NestedLoop 的循环总次数;“永远用小结果集驱动大的结果集”。
  • 优先优化 NestedLoop 的内层循环;
  • 保证 Join 语句中被驱动表上 Join 条件字段已经被索引;
  • 当无法保证被驱动表上的 Join 条件字段被索引且内存资源充足的前提下,不要太吝啬 JoinBuffer 的设置。

2)索引失效(应该避免)

CREATE TABLE staffs (

  id INT PRIMARY KEY AUTO_INCREMENT,

  NAME VARCHAR (24)  NULL DEFAULT '' COMMENT '姓名',

  age INT NOT NULL DEFAULT 0 COMMENT '年龄',

  pos VARCHAR (20) NOT NULL DEFAULT '' COMMENT '职位',

  add_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间'

) CHARSET utf8 COMMENT '员工记录表' ;


INSERT INTO staffs(NAME,age,pos,add_time) VALUES('z3',22,'manager',NOW());

INSERT INTO staffs(NAME,age,pos,add_time) VALUES('July',23,'dev',NOW());

INSERT INTO staffs(NAME,age,pos,add_time) VALUES('2000',23,'dev',NOW());

INSERT INTO staffs(NAME,age,pos,add_time) VALUES(null,23,'dev',NOW());

SELECT * FROM staffs;


ALTER TABLE staffs ADD INDEX idx_staffs_nameAgePos(name, age, pos);

①全值匹配我最爱

索引  idx_staffs_nameAgePos 建立索引时 以 name , age ,pos 的顺序建立的。全值匹配表示 按顺序匹配的。

ALTER TABLE staffs ADD INDEX idx_staffs_nameAgePos(name, age, pos);

②最佳左前缀法则:如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列

从最左开始,建索引的第一个字段不能丢失,如果要查询后面,中间的字段也不能不要!

原因:违背了最佳左前缀法则。

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

都能查出来,结果一样:

性能不同:

④存储引擎不能使用索引中范围条件右边的列

⑤尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select*

⑥mysql 在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描

⑦is null,is not null 也无法使用索引

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

结论:like '%abc%' type 类型会变成 all;like 'abc%' type 类型为 range ,算是范围,可以使用索引。(% like 加在右边)

CREATE TABLE `tbl_user` (

 `id` INT(11) NOT NULL AUTO_INCREMENT,

 `NAME` VARCHAR(20) DEFAULT NULL,

 `age` INT(11) DEFAULT NULL,

 email VARCHAR(20) DEFAULT NULL,

 PRIMARY KEY (`id`)

) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
 

#drop table tbl_user

INSERT INTO tbl_user(NAME,age,email) VALUES('1aa1',21,'b@163.com');

INSERT INTO tbl_user(NAME,age,email) VALUES('2aa2',222,'a@163.com');

INSERT INTO tbl_user(NAME,age,email) VALUES('3aa3',265,'c@163.com');

INSERT INTO tbl_user(NAME,age,email) VALUES('4aa4',21,'d@163.com');

INSERT INTO tbl_user(NAME,age,email) VALUES('aa',121,'e@163.com');

#before index

EXPLAIN SELECT *     FROM tbl_user WHERE NAME LIKE '%aa%';

EXPLAIN SELECT id,NAME,age,email  FROM tbl_user WHERE NAME LIKE '%aa%';


#create index

CREATE INDEX idx_user_nameAge ON tbl_user(name,age);

从上图可以看出,如果查询两边都有 ‘%’,因为 name 和 age 都建立了索引,而 id 是主键自带索引,所以查询这三个字段或者其中一个都是可以用到覆盖索引,效果较好!


但是如果查询的是 ‘*’,或者多了一个没有建立索引的字段‘email’,索引就用不上了,type 就会变成 all。所以,like 查询的时候,要么 ‘%’ 加在右边,如果两边都有,就用覆盖索引查询!

问题:解决 like '% 字符串 %'  索引不被使用的方法??

  • 可以使用主键索引
  • 使用覆盖索引,查询字段必须是建立覆盖索引字段

⑨字符串不加单引号索引失效

如图,name 本来是 varchar 类型,如果查询时赋值 2000 就是给它转变了数据类型,导致索引失效。

⑩少用or,用它连接时会索引失效

小总结:

3)例题

【建表语句】

create table test03(
 id int primary key not null auto_increment,
 c1 char(10),
 c2 char(10),
 c3 char(10),
 c4 char(10),
 c5 char(10)
);

insert into test03(c1,c2,c3,c4,c5) values('a1','a2','a3','a4','a5');
insert into test03(c1,c2,c3,c4,c5) values('b1','b2','b3','b4','b5');
insert into test03(c1,c2,c3,c4,c5) values('c1','c2','c3','c4','c5');
insert into test03(c1,c2,c3,c4,c5) values('d1','d2','d3','d4','d5');
insert into test03(c1,c2,c3,c4,c5) values('e1','e2','e3','e4','e5');

【建索引】
create index idx_test03_c1234 on test03(c1,c2,c3,c4);

//show index from test03;

【分析】

1)explain select * from test03 where c1='a1' and c2='a2' and c3='a3' and c4='a4'; 

2) explain select * from test03 where c1='a1' and c2='a2' and c4='a4' and c3='a3'; 


四个字段都建立了索引,查询顺序不一致也可以用得到索引。

3) explain select * from test03 where c1='a1' and c2='a2' and c3>'a3' and c4='a4';

用到了1,2,3,第四个没用到(范围之后全失效)


4) explain select * from test03 where c1='a1' and c2='a2' and c4>'a4' and c3='a3';

1,2,3,4都用到了,mysql 会自动调顺序



5) explain select * from test03 where c1='a1' and c2='a2' and c4='a4' order by c3;

统计到了1,2 的索引, c3作用在排序而不是查找,用到了,但是没有统计进来。

6) explain select * from test03 where c1='a1' and c2='a2' order by c3;

结果和上面一样。

7) explain select * from test03 where c1='a1' and c2='a2' order by c4; 

出现了filesort。

8) 

8.1 explain select * from test03 where c1='a1' and c5='a5' order by c2,c3; 

 只用c1一个字段索引,但是c2、c3用于排序,无filesort

8.2 explain select * from test03 where c1='a1' and c5='a5' order by c3,c2;

出现了filesort,我们建的索引是1234,它没有按照顺序来,3 2 颠倒了


 

9) explain select * from test03 where c1='a1' and c2='a2' order by c2,c3;

10)explain select * from test03 where c1='a1' and c2='a2' and c5='a5' order by c2,c3;       

用c1、c2两个字段索引,但是c2、c3用于排序,无filesort

explain select * from test03 where c1='a1' and c2='a2' and c5='a5' order by c3,c2;             

本例有常量c2的情况,和8.2对比

explain select * from test03 where c1='a1' and c5='a5' order by c3,c2;   



11)explain select * from test03 where c1='a1' and c4='a4' group by c2,c3;

12)explain select * from test03 where c1='a1' and c4='a4' group by c3,c2;



小结:

  • 定值(为常量)、范围(后面失效)还是排序,一般 order by 是给个范围;
  • group by是分组,分组之前基本上都需要进行排序,会有临时表产生。所以和 order by 的排序法则和索引优化原则几乎一样。只是 group by 还有 having。

4)一般性建议

  • 对于单键索引,尽量选择针对当前 Query 过滤性更好的索引
  • 在选择组合索引的时候,当前 Query 中过滤性最好的字段在索引字段顺序中,位置越靠前越好。
  • 在选择组合索引的时候,尽量选择可以能包含当前 Query 中的 where 子句中更多字段的索引
  • 尽可能通过分析统计信息和调整 Query 的写法来达到选择合适索引的目的

(三)查询截取分析

发布了48 篇原创文章 · 获赞 18 · 访问量 2792

猜你喜欢

转载自blog.csdn.net/weixin_44210965/article/details/105271516