MySQL 高级(四)索引性能分析 索引单表优化 索引两表优化 索引三表优化 索引优化案例 规律 索引面试题分析 优化总结口诀

MySQL 高级(四)索引优化分析

4.7 索引性能分析

前景知识:MySQL中有专门负责优化SELECT语句的优化器模块,可以通过计算分析系统中收集到的统计信息,为客户端请求的Query提供MySQL认为最优的执行计划

**如何获取MySQL的执行时间?**使用EXPLAIN 关键字

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

能够分析哪些信息?

  • SQL中涉及到表的读取顺序–>id
  • 数据读取操作的操作类型–>select_type
  • 哪些索引可以使用–>possible_keys
  • 哪些索引被实际使用–>key
  • 表之间的引用–>ref
  • 每张表有多少行被优化器查询–>rows

使用方法: EXPLAIN + SQL 语句

在这里插入图片描述

重要:各字段的解释
  • id:select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序

  • id的三种情况

    • id相同:执行顺序由上至下

在这里插入图片描述

  • id不同:如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行

在这里插入图片描述

  • id相同和不同同时存在:id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行

在这里插入图片描述


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

  • 值的类型:

    • id select_type 类型
      1 SIMPLE 简单的select查询,查询中不包含子查询或者union
      2 PRIMARY 查询中若包含任何复杂的子部分,最外层查询则被标记为PRIMARY
      3 SUBQUERY 在select或where列表中包含了子查询
      4 DERIVED 在from列表中包含的子查询被标记为DERIVED(衍生),MySQL会递归执行这些子查询,把结果放在临时表中
      5 UNION 若第二个select出现在union之后,则被标记为union;若union包含在from子句的子查询中,外层select将被标记DERIVED
      6 UNION RESULT 仓UNION表获取结果的select

  • table:显示这一行的数据关于哪张表

  • type:type显示的是访问类型,是较为重要的一个指标,常见取值有7种。结果值的最好到最坏依次是:system>const>eq_ref>ref>range>index>ALL。一般来说,得保证百万数据的查询达到range级别,最好能达到ref。
  • 值的分析:
    • system:表只有一条记录(等于系统表),这是const类型的特例,平时不会出现,这个也可以忽略不计

    • const:表示通过索引一次就找到了记录,const用于比较primary key或者unique索引。因为只匹配一行数据,索引很快。如将主键置域where 查询条件的列表中,MySQL就能将该查询转换为一个常量

    • eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描

    • ref:非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,它返回所有匹配某个单独值的行,它可能会找到多个符合条件的列,所有他应该属于查找和扫描的混合体

      • 在这里插入图片描述
    • range:只检索给定范围的行,使用一个索引来选择行。在where条件中出现了between、<、>、in等查询时出现。这种范围扫描索引比全表扫描要好,因为它只需要开始于索引的某一点,而结束于另一点,不用扫描全部索引

      • 在这里插入图片描述
    • index:Full Index Scan全索引扫描,index与ALL区别为index类型只遍历索引树。这通常比ALL快,因为索引文件通常比数据文件小。并且虽然ALL和INDEX都是读全表,但index是从内存索引中读的,all是从硬盘读的

    • ALL:Full Table Scan全表扫描


  • possible_keys:显示可能应用在这张表中的索引,一个或多个,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用
  • key:实际使用的索引。如果为null,则没有使用索引
  • key_len:表示索引中使用的字节数,可通过该列计算查询中使用索引的长度。在不损失精确性的情况下,长度越短越好。key_len显示的值为索引的最大可能长度,并非实际使用长度,即key_len是根据定义计算而得的,不是通过表内检索出的

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

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

  • Extra:包含不适合在其他列中显示但十分重要的额外信息
    • Using filesort:说明MySQL会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成的排序操作称为“文件排序”。
    • Using temporary:说明使用了临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于排序order by和分组查询 group by
    • Using index:表示相应的select操作中使用了覆盖索引,避免访问了表的数据行,效率不错。如果同时出现using where,表明索引被用来执行索引键值的查找;如果没有同时出现using where,表明索引用来读取数据而非执行查找操作
      • 覆盖索引:select的数据列只从索引中就能够取得,不必读取数据行,MySQL可以利用索引返回select列表中的字段,而不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖
    • Using where:表明使用了where过滤
    • Using jion buffer:表明使用了连接缓存
    • Impossible where:where子句的判断结果总是false,无法获取任何元素

初步使用EXPLAIN 分析SQL

在这里插入图片描述

分析

  1. 执行顺序1:执行id为4的行,select_type为union,说明第四个select是union的第二个select,最先执行 【select name,id from t2】
  2. 执行顺序2:执行id为3的行,查询表t1。因查询包含在from中,索引为select_type为derived(衍生) 【select id,name from ti where other_clounm=""】
  3. 执行顺序3:执行id为2的行,SUBQUERY表示这是一个子查询,使用了id作为主键索引。【select id from t3】
  4. 执行顺序4:执行id为1的行,PROMARY表示该查询为外层查询,table列被标记为<derived3> 表示查询结果来自一个衍生表,其中的3代表查询衍生自第三个select查询
  5. 执行顺序5:执行id为null的行,UNION_RESULT代表从union的临时表中读取行的阶段,table列的<union1,4>表示用第一个和第四个select的结果进行

4.8 索引单表优化

测试表结构

CREATE TABLE article (
	id INT4 UNSIGNED NOT NULL PRIMARY KEY auto_increment,
	author_id INT4 UNSIGNED NOT NULL,
	category_id INT4 UNSIGNED NOT NULL,
	views INT8 UNSIGNED NOT NULL,
	comments INT8 UNSIGNED NOT NULL,
	title VARBINARY ( 255 ) NOT NULL,
	content text NOT NULL 
)

INSERT INTO article(author_id,category_id,views,comments,title,content) 
VALUES
(1,1,1,1,'1','1'),
(2,2,2,2,'2','2'),
(1,1,3,3,'3','3');

SELECT * FROM article

案例:查询category_id为1且comments大于1的情况下,views最多的artcle_id.

EXPLAIN SELECT id  FROM article  WHERE category_id = 1  AND  comments > 1  ORDER BY views  LIMIT 1

未优化结果

在这里插入图片描述

分析:很显然。此时表中只有id字段有主键索引,type是ALL,即最坏的情况。Extra里还出现了Using filesort,也是最坏的情况。优化是必须的。

优化思路

  1. 索引优化的是whrere和orderby的条件,此sql中涉及到的条件字段有category_id、comments和views,使用这三个条件建立一个复合索引,并查看分析结果

    create index idx_article_ccv on article(category_id,comments,views)
    

在这里插入图片描述

  1. 结果显示,type变成了range,实现了优化,但Extra仍然出现了Using filesort,需要继续优化。

  2. Using filesort出现的原因,因为BTree索引的工作原理,会先排序category_id,如果遇到相同的category_id则再排序comments,遇到相同的comments再排序views;当comments字段在联合索引里处于中间位置时,因comments>1条件是一个范围值(range),MySQL无法利用索引再对后面的views部分进行检索,即range类型查询字段后面的索引无效(索引失效)

  3. 第二次索引尝试:使用category_id和views建立一个复合索引,并查看分析结果

    create index idx_article_cv on article(category_id,views)
    

    在这里插入图片描述

  4. 此时type上升未ref,未出现Using filesort,索引建立成功

总结:单表进行索引优化的重点在于分析where和order by关键字的字段,同时需要考虑索引失效的问题

4.9 索引两表优化

测试表结构

create table class(
id int8 UNSIGNED not null auto_increment,
card int8 UNSIGNED not null,
PRIMARY key(id)
);

CREATE TABLE book(
book_id int8 UNSIGNED not NULL auto_increment,
card int8 UNSIGNED not NULL,
PRIMARY KEY(book_id)
);
#各执行20次
INSERT INTO class(card) VALUES(FLOOR(RAND()*20));
INSERT INTO book(card) VALUES(FLOOR(RAND()*20));

案例:将两表使用card字段进行连接左或右连接

EXPLAIN SELECT	* FROM class LEFT JOIN book on class.card = book.card

未优化结果

在这里插入图片描述

优化思路分析

  1. 没有索引时,两表连接需要全表扫描,type为ALL类型,需要对连接的card字段建立索引

  2. 问题:应该建立在class表(左连接中的右表)还是book表(左连接中的左表)的card字段?

  3. 第一次尝试:对class表(左连接中的右表)的card字段建立索引,并分析结果

在这里插入图片描述

  1. 此时class表的分析type变为index,但扫描的行数rows仍然为20,并且出现了Using join buffer 使用连接缓存,没有优化

  2. 第二次尝试:对book表(左连接中的左表)的card字段建立索引,并分析结果

在这里插入图片描述

  1. 此时book表的分析type变为ref,且整条查询语句只扫描的21行,优化效果明显

  2. 为什么?这是由于左连接的特性所决定的。LEFT JION条件用于确定如何从右表搜索行,左表一定包含所有的内容;所有此时右边才是我们的关键点,一定需要建立索引

结论:对于两表之间关联的索引优化,关键在于根据具体连接的特性,分析出哪一张表更重要,建立索引。(规律:左连接时索引建立右表,右连接时索引建立左表

4.10 索引三表优化

测试表结构

create TABLE phone(
phone_id int(11) UNSIGNED not null auto_increment,
card int4 UNSIGNED not null,
PRIMARY key(phone_id)
);

#各执行20次
INSERT INTO phone(card) VALUES(FLOOR(RAND()*20));

案例:将三张表通过card字段进行连接

EXPLAIN SELECT * from class LEFT JOIN book on class.card= book.card LEFT JOIN phone ON class.card= phone.card

未优化的结果

在这里插入图片描述

索引优化思路

  1. 未优化时结果堪比爆炸,3表均为全表扫描,产生了连接缓存

  2. 给两个左连接的右表book、phone同时建立关于card字段的索引,并查看分析结果

    create index idx_book_card on book(card);
    create index idx_phone_card on phone(card);
    

在这里插入图片描述

  1. 此时book和phone表type变为ref,均只扫描一条记录,索引优化大大改善。

结论

  • 多表连接查询与两表相同,分析连接类型,建立索引字段(规律:左连接时索引建立右表,右连接时索引建立左表
  • 尽可能减少JOIN语句中的循环总次数。“永远用小的结果集驱动大的结果集
  • 保证join语句被驱动表(被连接表)的on字段已经被索引
  • 当无法保证被驱动表的join条件字段被索引且内存资源充足的前提下,不要太吝惜JoinBuffer的设置(修改mysql配置文件,加大JoinBuffer的内存资源从而加快join速度)

4.11 重要:索引优化案例,规律

测试表结构

CREATE TABLE staffs (
	id INT4 PRIMARY KEY auto_increment,
	name VARCHAR ( 24 ) NOT NULL DEFAULT '' COMMENT '姓名',
	age INT2 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('jack',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());

SELECT * from staffs

create index idx_staffs_nameAgePos on staffs(name,age,pos);
4.11.1 案例一:全值匹配我最爱
EXPLAIN select * from staffs WHERE name = 'july';
EXPLAIN select * from staffs WHERE name = 'july' and age = 25;
EXPLAIN select * from staffs WHERE name = 'july' and age = 25 and pos='dev';

在这里插入图片描述

​ 三条查询按照创建索引时指定的字段顺序逐个添加where条件,均使用了索引,实现了优化。但是当改变查询语句后(不查询name,直接通过age和pos字段查询):

EXPLAIN select * from staffs WHERE age = 25;
EXPLAIN select * from staffs WHERE age = 25 and pos='dev';

在这里插入图片描述

此时索引失效,原因:没有遵循最佳左前缀法则(案例二)

4.11.2 案例二:最佳左前缀法则: “带头大哥不能死”、“中间兄弟不能断”

​ 如果索引了多列(复合索引),要遵守最左前缀法则。指的是查询从索引最左前列开始“带头大哥不能死”)并且不跳过索引中的中间列“中间兄弟不能断”)。

当使用最左列索引,跳过中间列索引时,此时部分索引生效(name)

EXPLAIN select * from staffs WHERE name = 'jack'and pos='dev';

在这里插入图片描述

总结:可以类似将建立索引理解成造楼梯,索引字段的顺序就是楼梯的顺序,要想使用走楼梯,必须第一层楼梯(最左字段)存在。能走到第几层楼梯(使用到第几个索引字段),就看楼梯有没有断。

4.11.3 案例三:不在索引列上做任何操作(计算、函数、类型转换),会导致索引失效而转向全表扫描
explain select * from staffs where left(name,4) = 'jack';

在这里插入图片描述

4.11.4 案例四:存储引擎不能使用索引中范围条件(> < in like)右边的列
EXPLAIN SELECT * FROM staffs WHERE name = 'jack' and age >11 and pos ='manager'

​ 当查询的where条件后出现范围查询条件,如大于小于、in、like等时,范围条件右边的列无法使用索引,因此这条查询语句中使用了索引中的name和age

在这里插入图片描述

4.11.5 案例五:尽量使用覆盖索引(只访问索引的查询),减少select*

​ select时最佳的查询效果就是使用覆盖索引,此时不必读取数据行,MySQL可以利用索引返回select列表中的字段,而不必根据索引再次读取数据文件,因此应该按需取数据,减少select*

EXPLAIN SELECT name,age,pos FROM staffs WHERE name = 'jack' and age = 11 and pos ='manager'

在这里插入图片描述

4.11.6 案例六:MySQL在使用不等于(!= 或者<>)的时候无法使用索引会导致全表扫描
EXPLAIN SELECT * from staffs WHERE name != 'jack';
EXPLAIN SELECT * from staffs WHERE name <> 'jack';

在这里插入图片描述

4.11.7 案例七:is null,is not null 也无法使用索引
EXPLAIN SELECT * from staffs WHERE name is null;
EXPLAIN SELECT * from staffs WHERE name is not null;

在这里插入图片描述

4.11.8案例八:like以通配符开头’%abc’,索引失效会变成全表扫描操作

​ 使用like进行通配符匹配时,%加开头会导致索引失效,因此最好加右边(视情况而定)

在这里插入图片描述

问题:因为实际情况中确实有需要左右都加通配符,有没有like %字符串时索引仍然有效的方法

解决方法:可以使用覆盖索引来解决,对需要select的字段建立索引,当满足覆盖索引的条件后,查询的type会优化为index,并且使用了索引

EXPLAIN SELECT name,age,pos FROM staffs WHERE name like '%jack%'

在这里插入图片描述

4.11.9 案例九:字符串不加单引号索引失效

前景:staffs表中name字段为varchar类型。其中存在一条记录 name=‘2000’,此时使用一下两条语句都可以将记录查出,因为name字段为varchar类型,使用第二条查询语句name=2000时,MySQL底层会进行自动类型转换此时索引会失效

SELECT * from staffs WHERE name ='2000';
SELECT * from staffs WHERE name =2000;

在这里插入图片描述
在这里插入图片描述

4.11.10 案例十:少用or,用它来连接时会索引失效

​ where的查询条件使用or导致索引失效

EXPLAIN SELECT * from staffs WHERE name = 'jack' or name ='july';

在这里插入图片描述

4.12 索引面试题分析

测试表结构

CREATE TABLE test03 (
	id INT4 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');

SELECT * FROM test03;
--建索引
create index idx_test03_c1234 on test03(c1,c2,c3,c4);

问题:我们创建了复合索引idx_test03_c1234,根据一下sql分析索引的使用情况?

  • EXPLAIN SELECT * from test03 where c1 = 'a1' and c2 = 'a2' and c3 = 'a3' and c4 = 'a4'
    
    • 全值匹配,使用了索引c1 c2 c3 c4
  • EXPLAIN SELECT * from test03 where c1 = 'a1' and c3 = 'a2' and c4 = 'a3' and c2 = 'a4'
    
    • 也是全值匹配,使用了索引c1 c2 c3 c4,虽然where中字段的顺序与创建索引的顺序不一致,但MySQL架构中第二层的优化器会将sql语句根据索引中字段的顺序进行优化,对where的条件重新排序(但是最好还是怎么建立索引就按顺序使用,减少底层的一次优化)
  • EXPLAIN SELECT * from test03 where c1 = 'a1' and c2 = 'a2' and c3 > 'a3' and c4 = 'a4'
    
    • 部分使用了索引,对于c1和c2使用索引进行了常量const的查找,对于c3使用了索引的部分排序,因为c3是一个范围查找,因此c4没有使用到索引
  • EXPLAIN SELECT * from test03 where c1 = 'a1' and c2 = 'a2' and c4 = 'a4' ORDER BY c3
    
    • 对于含有order by 的需要特别分析,这个查询中c1和c2使用了索引进行常量查找,c3使用索引进行排序,c4没有使用索引
  • EXPLAIN SELECT * from test03 where c1 = 'a1' and c2 = 'a2' ORDER BY c4
    
    • 因为查询条件中没有使用c3,跳过c3直接对c4进行排序,因此c4无法使用索引,因此mysql底层会使用文件排序 Using filesort(“中间兄弟不能断”
  • EXPLAIN SELECT * FROM test03 where c1 = 'a1' and c5 = 'a5' order by c2,c3
    
    • 此时c1使用索引进行查找,c2、c3使用索引进行排序,无filesort

    • 若order by的顺序与索引建立顺序不同,即order by c3,c2 ,会出现Using filesort

  • EXPLAIN SELECT c2,c3 FROM test03 where c1 = 'a1' and c4 = 'a4' GROUP BY c2,c3
    
    • c1使用了索引进行查找,c2和c3进行的分组查询,导致c4无法进行索引查找
  • EXPLAIN SELECT c2,c3 FROM test03 where c1 = 'a1' and c4 = 'a4' GROUP BY c3,c2
    
    • 相比于上一条sql,分组查询的顺序发生了改变,没有按照索引建立的顺序,导致产生了using temp

4.13 优化总结口诀

全值匹配我最爱,最左前缀要遵守。

带头大哥不能死,中间兄弟不能断。

索引列上少计算,范围之后全失效。

LIKE百分写最右,覆盖索引不写星。

不等空指还有or,索引失效要少用。

VAR引号不可丢,SQL高级也不难!

猜你喜欢

转载自blog.csdn.net/weixin_44634197/article/details/108902601