MySQL高级——索引优化

一、索引分析

1.1 单表索引分析

数据准备:

DROP TABLE IF EXISTS `article`;
CREATE TABLE IF NOT EXISTS `article`(
`id` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
`author_id` INT(10) UNSIGNED NOT NULL COMMENT '作者id',
`category_id` INT(10) UNSIGNED NOT NULL COMMENT '分类id',
`views` INT(10) UNSIGNED NOT NULL COMMENT '被查看的次数',
`comments` INT(10) UNSIGNED NOT NULL COMMENT '回帖的备注',
`title` VARCHAR(255) NOT NULL COMMENT '标题',
`content` VARCHAR(255) NOT NULL COMMENT '正文内容'
) COMMENT '文章';
INSERT INTO `article`(`author_id`, `category_id`, `views`, `comments`, `title`, `content`) VALUES(1,1,1,1,'1','1');
INSERT INTO `article`(`author_id`, `category_id`, `views`, `comments`, `title`, `content`) VALUES(2,2,2,2,'2','2');
INSERT INTO `article`(`author_id`, `category_id`, `views`, `comments`, `title`, `content`) VALUES(3,3,3,3,'3','3');
INSERT INTO `article`(`author_id`, `category_id`, `views`, `comments`, `title`, `content`) VALUES(1,1,3,3,'3','3');
INSERT INTO `article`(`author_id`, `category_id`, `views`, `comments`, `title`, `content`) VALUES(1,1,4,4,'4','4');

案例:查询category_id为1且comments大于1的情况下,views最多的article_id
1、编写SQL语句并查看SQL执行计划。

# 1、sql语句
SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
# 2、sql执行计划
mysql> EXPLAIN SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: article
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 5
     filtered: 20.00
        Extra: Using where; Using filesort  # 产生了文件内排序,需要优化SQL
1 row in set, 1 warning (0.00 sec)

2、创建索引idx_article_ccv

CREATE INDEX idx_article_ccv ON article(category_id,comments,views);

3、查看当前索引。
show index
4、查看现在SQL语句的执行计划。
explain
我们发现,创建符合索引idx_article_ccv之后,虽然解决了全表扫描的问题,但是在order by排序的时候没有用到索引,MySQL居然还是用的Using filesort,为什么?
5、我们试试把SQL修改为SELECT id,author_id FROM article WHERE category_id = 1 AND comments = 1 ORDER BY views DESC LIMIT 1;看看SQL的执行计划。
explain
推论:当comments > 1的时候order by排序views字段索引就用不上,但是当comments = 1的时候order by排序views字段索引就可以用上!!!所以,范围之后的索引会失效。
6、我们现在知道范围之后的索引会失效,原来的索引idx_article_ccv最后一个字段views会失效,那么我们如果删除这个索引,创建idx_article_cv索引呢????

/* 创建索引 idx_article_cv */
CREATE INDEX idx_article_cv ON article(category_id,views);

查看当前的索引
show index
7、当前索引是idx_article_cv,来看一下SQL执行计划。
explain

1.2 两表索引分析

数据准备:

DROP TABLE IF EXISTS `class`;
DROP TABLE IF EXISTS `book`;
CREATE TABLE IF NOT EXISTS `class`(
`id` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
`card` INT(10) UNSIGNED NOT NULL COMMENT '分类' 
) COMMENT '商品类别';
CREATE TABLE IF NOT EXISTS `book`(
`bookid` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
`card` INT(10) UNSIGNED NOT NULL COMMENT '分类'
) COMMENT '书籍';

两表连接查询的SQL执行计划:

1、不创建索引的情况下,SQL的执行计划。
explain
bookclass两张表都是没有使用索引,全表扫描,那么如果进行优化,索引是创建在book表还是创建在class表呢?下面进行大胆的尝试!
2、左表(book表)创建索引。
创建索引idx_book_card

/* 在book表创建索引 */
CREATE INDEX idx_book_card ON book(card);

book表中有idx_book_card索引的情况下,查看SQL执行计划
explain
3、删除book表的索引,右表(class表)创建索引。
创建索引idx_class_card

/* 在class表创建索引 */
CREATE INDEX idx_class_card ON class(card);

class表中有idx_class_card索引的情况下,查看SQL执行计划
explain
由此可见,左连接将索引创建在右表上更合适,右连接将索引创建在左表上更合适。

1.3 三张表索引分析

数据准备:

DROP TABLE IF EXISTS `phone`;
CREATE TABLE IF NOT EXISTS `phone`(
`phone_id` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
`card` INT(10) UNSIGNED NOT NULL COMMENT '分类' 
) COMMENT '手机';

三表连接查询SQL优化
1、不加任何索引,查看SQL执行计划。
explain
2、根据两表查询优化的经验,左连接需要在右表上添加索引,所以尝试在book表和phone表上添加索引。

/* 在book表创建索引 */
CREATE INDEX idx_book_card ON book(card);
/* 在phone表上创建索引 */
CREATE INDEX idx_phone_card ON phone(card);

再次执行SQL的执行计划
explain

1.4 结论

JOIN语句的优化:

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

二、索引失效(索引失效的情况)

数据准备

CREATE TABLE `staffs`(
`id` INT(10) PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(24) NOT NULL DEFAULT '' COMMENT '姓名',
`age` INT(10) NOT NULL DEFAULT 0 COMMENT '年龄',
`pos` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '职位',
`add_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间'
)COMMENT '员工记录表';
INSERT INTO `staffs`(`name`,`age`,`pos`) VALUES('Ringo', 18, 'manager');
INSERT INTO `staffs`(`name`,`age`,`pos`) VALUES('张三', 20, 'dev');
INSERT INTO `staffs`(`name`,`age`,`pos`) VALUES('李四', 21, 'dev');
/* 创建索引 */
CREATE INDEX idx_staffs_name_age_pos ON `staffs`(`name`,`age`,`pos`);

2.1 全表扫描我最爱(标准做法)

在这里插入图片描述
可以看到,复合索引都被用到了,并且SQL中查询字段的顺序,跟使用索引中字段的顺序,没有关系。优化器会在不影响 SQL 执行结果的前提下,自动地优化

结论:全职匹配我最爱指的是,查询的字段按照顺序在索引中都可以匹配到

2.2 最佳左前缀法则

在这里插入图片描述
总结:
最佳左前缀法则:如果索引是多字段的复合索引,要遵守最佳左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的字段。
口诀:带头大哥不能死,中间兄弟不能断。

2.3 索引列上不计算

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

通过分析两条SQL的执行计划来分析性能。
explain

由此可见,在索引列上进行计算,会使索引失效。
口诀:索引列上不计算。

2.4 索引列上不能有范围查询

查看上述SQL的执行计划

explain可以看出,当对age字段使用范围查询后,使得范围后面的索引失效了
建议:将可能做范围查询的字段的索引顺序放在最后

由此可知,查询范围的字段使用到了索引,但是范围之后的索引字段会失效。
口诀:范围之后全失效。

2.5 尽量使用覆盖索引

在写SQL的不要使用SELECT *,用什么字段就查询什么字段。

/* 没有用到覆盖索引 */
EXPLAIN SELECT * FROM `staffs` WHERE `name` = 'Ringo' AND `age` = 18 AND `pos` = 'manager';
/* 用到了覆盖索引 */
EXPLAIN SELECT `name`, `age`, `pos` FROM `staffs` WHERE `name` = 'Ringo' AND `age` = 18 AND `pos` = 'manager';

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

2.6 使用不等会失效

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

mysql5.5.48:
在这里插入图片描述
mysql8.0.18:
在这里插入图片描述

2.7 is null、is not null也无法使用索引

在这里插入图片描述

2.8 like百分加右边

like以通配符开头(’%abc…’)mysql索引失效会变成全表扫描的操作
在这里插入图片描述
模糊查询时,尽量不要把%通配符写在开头。只有当百分号在右边,索引才会生效
在这里插入图片描述

如果一定要使用%like,而且还要保证索引不失效,那么使用覆盖索引来编写SQL。

# 创建索引
CREATE INDEX idx_user_nameAge ON tbl_user(name,age);
/* 使用到了覆盖索引 */
EXPLAIN SELECT `id` FROM `staffs` WHERE `name` LIKE '%in%';
/* 使用到了覆盖索引 */
EXPLAIN SELECT `name` FROM `staffs` WHERE `name` LIKE '%in%';
/* 使用到了覆盖索引 */
EXPLAIN SELECT `age` FROM `staffs` WHERE `name` LIKE '%in%';
/* 使用到了覆盖索引 */
EXPLAIN SELECT `pos` FROM `staffs` WHERE `name` LIKE '%in%';
/* 使用到了覆盖索引 */
EXPLAIN SELECT `id`, `name` FROM `staffs` WHERE `name` LIKE '%in%';
/* 使用到了覆盖索引 */
EXPLAIN SELECT `id`, `age` FROM `staffs` WHERE `name` LIKE '%in%';
/* 使用到了覆盖索引 */
EXPLAIN SELECT `id`,`name`, `age`, `pos` FROM `staffs` WHERE `name` LIKE '%in';
/* 使用到了覆盖索引 */
EXPLAIN SELECT `id`, `name` FROM `staffs` WHERE `pos` LIKE '%na';
/* 索引失效 全表扫描 */
EXPLAIN SELECT `name`, `age`, `pos`, `add_time` FROM `staffs` WHERE `name` LIKE '%in';

模糊查询百分号一定加前边
口诀:覆盖索引保两边。

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

因为name为varchar型,mysql会在底层进行隐式的类型转换。
在这里插入图片描述
所以,varchar类型的字段不能忘了加引号,因为会导致进行全表扫描。

2.10 少用or,用它来连接时会索引失效口

mysql5.5.48:
在这里插入图片描述
mysql8.0.18
这里的索引没失效,应该是mysql版本升级后的优化效果,老师的是5.5,虽然这里索引没失效,不过还是尽量少用or。
在这里插入图片描述

小练习:
假设index(a,b,c)

Where语句 索引是否被使用
where a = 3 Y,使用到a
where a = 3 and b = 5 Y,使用到a,b
where a = 3 and b = 5 Y,使用到a,b,c
where b = 3 或者 where b = 3 and c = 4 或者 where c = 4 N,没有用到a字段
where a = 3 and c = 5 使用到a,但是没有用到c,因为b断了
where a = 3 and b > 4 and c = 5 使用到a,b,但是没有用到c,因为c在范围之后
where a = 3 and b like ‘kk%’ and c = 4 Y,a,b,c都用到
where a = 3 and b like ‘%kk’ and c = 4 只用到a
where a = 3 and b like ‘%kk%’ and c = 4 只用到a
where a = 3 and b like ‘k%kk%’ and c = 4 Y,a,b,c都用到

三、面试题分析和索引优化的一般性建议

3.1 面试题分析

数据准备

/* 创建表 */
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','b22','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`);
  1. 第一题:

    explain select * from test03 where c1='a1' and c2='a2' and c4='a4' and c3='a3';
    explain select * from test03 where c4='a4' and c3='a3' and c2='a2' and c1='a1';
    
    # 复合索引并不会失效
    # 因为mysql底层的优化器会进行优化,所以可以使用到创建的索引,尽量不要这么写,避免mysql底层进行翻译和转换。
    

    在这里插入图片描述

  2. 第二题

    explain select * from test03 where c1='a1' and c2='a2' and c3>'a3' and c4='a4';
    
    # 范围之后全失效(包含那个进行范围查找的字段,范围查找字段之后失效)
    

    在这里插入图片描述

  3. 第三题

    explain select * from test03 where c1='a1' and c2='a2' and c4>'a4' and c3='a3';
    
    # 4个字段都生效
    # 因为底层会进行优化,把c3提到c4前面,然后c4范围之后全失效,但是c4是生效的,c4之后已经没有字段了,所以4个字段都生效。
    

    在这里插入图片描述

  4. 第四题

    explain select * from test03 where c1='a1' and c2='a2' and c4='a4' order by c3;
    
    # c1,c2,c3都用到了
    # 因为索引有两大功能:查找和排序,c3在这里查找没用到,而是用到了排序,但是没统计到里面。
    

    在这里插入图片描述

  5. 第五题

    explain select * from test03 where c1='a1' and c2='a2' order by c3;
    
    # 结果和第四题一样
    # 说明在第四题里,也底层也进行了优化,把c3提到c4前面,然后由于c3为排序,所以c3排序完后,不到c4就结束了。
    

    在这里插入图片描述

  6. 第六题

    explain select * from test03 where c1='a1' and c2='a2' order by c4;
    
    # 中间兄弟不能断
    # 因为没c3,想到c4,底层就会进行文件内排序。
    

    在这里插入图片描述

  7. 第七题

    explain select * from test03 where c1='a1' and c5='a5' order by c2,c3;
    
    # c1、c2、c3都用到了索引,c2、c3用于排序,不统计到里面。
    # 因为c5不在复合索引内,所以没有filesort
    

    在这里插入图片描述

  8. 第八题

    explain select * from test03 where c1='a1' and c5='a5' order by c3,c2;
    
    # 由于c2和c3的位置颠倒了,所以出现了filesort
    # 结果和第七题一样,但中间的过程不一样,多了一个using filesort
    # 所以要按照创建的复合索引的顺序进行书写
    

    在这里插入图片描述

  9. 第九题

    explain select * from test03 where c1='a1' and c2='a2' order by c2,c3;
    
    # c1、c2用于查找,c2、c3用于排序
    # 按照查找和排序都是按照顺序来的,所以没问题
    

    在这里插入图片描述

  10. 第十题

    explain select * from test03 where c1='a1' and c2='a2' and c5='a5' order by c2,c3;
    
    # c1、c2用于查找,c2、c3用于排序
    # 结果与第九题类似,所以跟c5没关系,没问题。
    

    在这里插入图片描述

  11. 第十一题

    explain select * from test03 where c1='a1' and c2='a2' and c5='a5' order by c3,c2;
    
    #第八题:explain select * from test03 where c1='a1' and c5='a5' order by c3,c2;
    #(出现filesort)
    
    # 因为在第十一题这里,c2先进行查找,变成一个常量,用一个常量去order by 'a2' 是无效的,所以第十一题的order by c3,c2 等同于 order by c3
    

    在这里插入图片描述

  12. 第十二题

    explain select * from test03 where c1='a1' and c5='a5' order by c3,c2;
    
    # 出现filesort
    # 因为c2和c3没有按照索引的顺序
    

    在这里插入图片描述

  13. 第十三题

    explain select * from test03 where c1='a1' and c4='a4' group by c2,c3;
    
    # c4没用到索引,否则会统计到里面
    # c1、c2、c3用到索引,c2、c3用来排序,没统计到里面
    

    在这里插入图片描述

  14. 第十四题

    explain select * from test03 where c1='a1' and c4='a4' group by c3,c2;
    
    # 出现temporary(创建临时表)
    # c1用到了索引,c2、c3、c4都失效了
    

    在这里插入图片描述
    结论:group by 基本上都需要进行排序,索引优化几乎和ORDER BY一致,但如果错乱的话会有临时表产生

/* 最好索引怎么创建的,就怎么用,按照顺序使用,避免让MySQL再自己去翻译一次 */
/* 1.全值匹配 用到索引c1 c2 c3 c4全字段 */
EXPLAIN SELECT * FROM `test03` WHERE `c1` = 'a1' AND `c2` = 'a2' AND `c3` = 'a3' AND `c4` = 'a4';
/* 2.用到索引c1 c2 c3 c4全字段 MySQL的查询优化器会优化SQL语句的顺序*/
EXPLAIN SELECT * FROM `test03` WHERE `c1` = 'a1' AND `c2` = 'a2' AND `c4` = 'a4' AND `c3` = 'a3';
/* 3.用到索引c1 c2 c3 c4全字段 MySQL的查询优化器会优化SQL语句的顺序*/
EXPLAIN SELECT * FROM `test03` WHERE `c4` = 'a4' AND `c3` = 'a3' AND `c2` = 'a2' AND `c1` = 'a1';
/* 4.用到索引c1 c2 c3字段,c4字段失效,范围之后全失效 */
EXPLAIN SELECT * FROM `test03` WHERE `c1` = 'a1' AND `c2` = 'a2' AND `c3` > 'a3' AND `c4` = 'a4';
/* 5.用到索引c1 c2 c3 c4全字段 MySQL的查询优化器会优化SQL语句的顺序*/
EXPLAIN SELECT * FROM `test03` WHERE `c1` = 'a1' AND `c2` = 'a2' AND `c4` > 'a4' AND `c3` = 'a3';
/* 
   6.用到了索引c1 c2 c3三个字段, c1和c2两个字段用于查找,  c3字段用于排序了但是没有统计到key_len中,c4字段失效
*/
EXPLAIN SELECT * FROM `test03` WHERE `c1` = 'a1' AND `c2` = 'a2' AND `c4` = 'a4' ORDER BY `c3`;
/* 7.用到了索引c1 c2 c3三个字段,c1和c2两个字段用于查找, c3字段用于排序了但是没有统计到key_len中*/
EXPLAIN SELECT * FROM `test03` WHERE `c1` = 'a1' AND `c2` = 'a2' ORDER BY `c3`;
/* 
   8.用到了索引c1 c2两个字段,c4失效,c1和c2两个字段用于查找,c4字段排序产生了Using filesort说明排序没有用到c4字段 
*/
EXPLAIN SELECT * FROM `test03` WHERE `c1` = 'a1' AND `c2` = 'a2' ORDER BY `c4`;
/* 9.用到了索引c1 c2 c3三个字段,c1用于查找,c2和c3用于排序 */
EXPLAIN SELECT * FROM `test03` WHERE `c1` = 'a1' AND `c5` = 'a5' ORDER BY `c2`, `c3`;
/* 10.用到了c1一个字段,c1用于查找,c3和c2两个字段索引失效,产生了Using filesort */
EXPLAIN SELECT * FROM `test03` WHERE `c1` = 'a1' AND `c5` = 'a5' ORDER BY `c3`, `c2`;
/* 11.用到了c1 c2 c3三个字段,c1 c2用于查找,c2 c3用于排序 */
EXPLAIN SELECT * FROM `test03` WHERE `c1` = 'a1' AND  `c2` = 'a2' ORDER BY c2, c3;
/* 12.用到了c1 c2 c3三个字段,c1 c2用于查找,c2 c3用于排序 */
EXPLAIN SELECT * FROM `test03` WHERE `c1` = 'a1' AND  `c2` = 'a2' AND `c5` = 'a5' ORDER BY c2, c3;
/* 
   13.用到了c1 c2 c3三个字段,c1 c2用于查找,c2 c3用于排序 没有产生Using filesort 
      因为之前c2这个字段已经确定了是'a2'了,这是一个常量,再去ORDER BY c3,c2 这时候c2已经不用排序了!
      所以没有产生Using filesort 和(10)进行对比学习!
*/
EXPLAIN SELECT * FROM `test03` WHERE `c1` = 'a1' AND `c2` = 'a2' AND `c5` = 'a5' ORDER BY c3, c2;
/* GROUP BY 表面上是叫做分组,但是分组之前必定排序。 */
/* 14.用到c1 c2 c3三个字段,c1用于查找,c2 c3用于排序,c4失效 */
EXPLAIN SELECT * FROM `test03` WHERE `c1` = 'a1' AND `c4` = 'a4' GROUP BY `c2`,`c3`;
/* 15.用到c1这一个字段,c4失效,c2和c3排序失效产生了Using filesort */
EXPLAIN SELECT * FROM `test03` WHERE `c1` = 'a1' AND `c4` = 'a4' GROUP BY `c3`,`c2`;

3.2 索引优化的一般性建议

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

口诀:

带头大哥不能死。
中间兄弟不能断。
索引列上不计算。
范围之后全失效。
覆盖索引尽量用。
不等有时会失效。
like百分加右边。
字符要加单引号。
一般SQL少用or。

猜你喜欢

转载自blog.csdn.net/weixin_44630656/article/details/115394796