mysql索引、索引优化(这一篇包括所有)

二、索引优化分析

1、性能下降SQL慢、执行时间长、等待时间长

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

2、常见通用的Join查询

  • SQL执行顺序
    • 手写
      在这里插入图片描述
    • 机读
      在这里插入图片描述
    • 总结
      在这里插入图片描述
  • Join图
    在这里插入图片描述
  • 创建SQL
CREATE TABLE `tbl_dept`(
	`id` INT(11) NOT NULL AUTO_INCREMENT,
	`deptName` VARCHAR(30) DEFAULT NULL,
	`locAdd` VARCHAR(40) DEFAULT NULL,
	PRIMARY KEY (`id`)
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

CREATE TABLE `tbl_emp`(
	`id` INT(11) NOT NULL AUTO_INCREMENT,
	`name` VARCHAR(20) DEFAULT NULL,
	`deptId` INT(11) DEFAULT NULL,
	PRIMARY KEY (`id`),
	KEY `fk_dept_id`(`deptId`)
	#CONSTRAINT `fk_dept_id` FOREIGN KEY (`deptId`) REFERENCES `tbl_dept`(`id`)
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

INSERT INTO tbl_dept(deptName,locAdd) VALUES('RD',11);
INSERT INTO tbl_dept(deptName,locAdd) VALUES('HR',12);
INSERT INTO tbl_dept(deptName,locAdd) VALUES('MK',13);
INSERT INTO tbl_dept(deptName,locAdd) VALUES('MIS',14);
INSERT INTO tbl_dept(deptName,locAdd) VALUES('FD',15);

INSERT INTO tbl_emp(name,deptId) VALUES('z3',1);
INSERT INTO tbl_emp(name,deptId) VALUES('z4',1);
INSERT INTO tbl_emp(name,deptId) VALUES('z5',1);

INSERT INTO tbl_emp(name,deptId) VALUES('w5',2);
INSERT INTO tbl_emp(name,deptId) VALUES('w6',2);

INSERT INTO tbl_emp(name,deptId) VALUES('s7',3);
INSERT INTO tbl_emp(name,deptId) VALUES('s8',4);
INSERT INTO tbl_emp(name,deptId) VALUES('s9',51);
  • 7种JOIN
select * from tbl_dept;
select * from tbl_emp;
select * from tbl_dept,tbl_emp;

--1.
select * from tbl_emp a INNER JOIN tbl_dept b ON a.deptId = b.id;
--2.
select * from tbl_emp a LEFT JOIN tbl_dept b ON a.deptId = b.id;
--3.
select * from tbl_emp a RIGHT JOIN tbl_dept b ON a.deptId = b.id;
--4.
select * from tbl_emp a LEFT JOIN tbl_dept b ON a.deptId = b.id where b.id is null;
--5.
select * from tbl_emp a RIGHT JOIN tbl_dept b ON a.deptId = b.id where a.deptId is null;

--6.
-- mysql不支持这种写法,oracle支持。
select * from tbl_emp a FULL JOIN tbl_dept b ON a.deptId = b.id;
-- 在mysql中这么写全连接
-- mysql 不支持 FULL JOIN,但是可以用 left join union right join 代替。
select * from tbl_emp a LEFT JOIN tbl_dept b ON a.deptId = b.id 
UNION
select * from tbl_emp a RIGHT JOIN tbl_dept b ON a.deptId = b.id;

--7.
select * from tbl_emp a LEFT JOIN tbl_dept b ON a.deptId = b.id 
where b.id IS NULL
UNION
select * from tbl_emp a RIGHT JOIN tbl_dept b ON a.deptId = b.id 
where a.deptId IS NULL;

3、索引简介

1、是什么

MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。

可以得到索引的本质:索引是数据结构

  • 索引的目的在于提高查询效率,可以类比字典。
  • 如果要查 “mysql” 这个单词,我们肯定需要定位到 m 字母,然后从下往下找到 y 字母,再找到剩下的sql。
  • 如果没有索引,那么你可能需要 a–z,如果我想找到Java开头的单词呢?或者Oracle开头的单词呢?
  • 是不是觉得如果没有索引,这个事情根本无法完成?

你可以简单理解为 “排好序的快速查询数据结构”。

  1. 详解(重要)

在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。下图就是一种可能的索引方式示例:
在这里插入图片描述
左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址。

为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取到相应数据,从而快速的检索出符合条件的记录。

  1. 结论

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

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

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

2、优势

类似大学图书馆建书目索引,提高数据检索的效率,降低数据库的IO成本。

通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗。

3、劣势

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

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

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

4、mysql索引分类

  • 单值索引

    即一个索引只包含单个列,一个表可以有多个单列索引。
    注意: 一张表索引不要超过5个。

  • 唯一索引

    索引列的值必须唯一,但允许有空值。

  • 复合索引

    即一个索引包含多个列。

  • 基本语法

    • 创建

      • CREATE [UNIQUE ] INDEX indexName ON mytable(columnname(length));

        如果是CHAR,VARCHAR类型,length可以小于字段实际长度;

        如果是BLOB和TEXT类型,必须制定length。

      • ALTER mytable ADD [UNIQUE ] INDEX [indexName] ON (columnname(length));

    • 删除

      • DROP INDEX [indexName] ON mytable;
    • 查看

      • SHOW INDEX FROM table_name;
    • 使用ALTER命令
      有四种方式来添加数据表的索引:

      • ALTER TABLE tbl_name ADD PRIMARY KEY (column_list):该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL。
      • ALTER TABLE tbl_name ADD UNIQUE index_name (column_list):这条语句创建索引的值必须是唯一的(除了NULL外,NUL可能会出现多次)。
      • ALTER TABLE tbl_name ADD INDEX index_name (column_list):添加普通索引,索引值可出现多次。
      • ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list):该语句指定了索引为FULLTEXT,用于全文索引。

5、mysql索引结构

  • BTree索引

    检索原理
    在这里插入图片描述

【初始化介绍】
一颗b+树,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),
如磁盘块包含数据项17和35,包含指针P1、P2、P3,
P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。
真实的数据存在于叶子节点即3、5、9、10、13、15、28、29、36、60、75、79、90、99。
非叶子节点只不存储真实的数据,只存储指引搜索方向的数据项,如17、35并不真实存在于数据表中。

【查找过程】
如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的10)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。

真实的情况是,3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。

  • Hash索引
  • full-text全文索引
  • R-Tree索引

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

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

7、哪些情况不需要创建索引

  1. 表记录太少。

  2. 经常增删改查的表

    Why:提交了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。

    因为更新表时,mysql不仅要保存数据,还要保存一下索引文件。

  3. 数据重复且分布平均的表字段,因此应该只为最经常查询和最经常排序的数据列建立索引。

    注意,如果某个数据列包含多重复的内容,为他创建所以就没有太大的实际效果。

4、性能分析

1、mysql Query Optimizer

1 Mysq|中有专门负责优化SELECT语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的Query提供他认为最优的执行计划(他认为最优的数据检索方式,但不见得是DBA认为是最优的,这部分最耗费时间)

2 当客户端向MySQL请求一 条Query,命令解析器模块完成请求分类,区别出是SELECT并转发给MySQL Query Optimizer时,MySQL Query Optimizer首先会对整条Query进行优化,处理掉一些 常量表达式的预算,直接换算成常量值。并对Query中的查询条件进行简化和转换,如去掉一些无用或显而易见的条件、结构调整等。然后分析Query中的Hint信息(如果有),看显示Hint信息是否可以完全确定该Query的执行计划。如果没有Hint或Hint信息还不足以完全确定执行计划,则会读取所涉及对象的统计信息,根据Query进行写相应的计算分析,然后再得出最后的执行计划。

2、MySQL常见瓶颈

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

3、Explain

  1. 是什么(查看执行计划)

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

官网地址:http://dev.mysql.com/doc/refman/5.5/en/explain-output.html

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

Explain + SQL语句

执行计划包含的信息:
在这里插入图片描述

4、各个字段解释

1)id【重要】

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

三种情况

  • id相同,执行顺序由上至下
    在这里插入图片描述

  • id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
    在这里插入图片描述

  • id相同不同。同时存在
    在这里插入图片描述
    衍生:DERIVED

2)select_type

有哪些:
在这里插入图片描述

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

  • SIMPLE ---- 简单的select查询,查询中不包含子查询或者UNION。

  • PRIMARY -----查询中若包含任何复杂的子部分,最外层查询则被标记。

  • SUBQUERY -----在SELECT或WHERE列表中包含了子查询。

  • DERIVED -----在FROM列表中包含的子查询被标记为DERIVED(衍生) MySQL会递归执行这些子查询,把结果放在临时表里。

  • UNION -----若第二个SELECT出现UNION之后,则被标记为UNION。
    若UNION包含在FROM子句的子查询中,外层select将被标记为:DERIVED

  • UNION RESULT ------从UNION表获取结果的SELECT。

3)table

显示这一行的数据是关于哪张表的。

4)type【重要】
在这里插入图片描述
访问类型排列:
type显示的是访问类型,是较为重要的一个指标,结果值从最好到最坏依次是:
system>const>eq_ref>ref>fulltext>ref_or_null>index_merge>unique_subquery>index_subquery>range>index>ALL

显示查询是用来何种类型,从最好到最差依次是:system>const>eq_ef>ref>range>index>ALL

  • 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:Per Full Table Scan,将遍历全表以找到匹配的行。

注:一般来说,得保证查询至少达到range级别,最好能达到ref。

5)possible_keys(理论上的索引)

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

6)key【重要】

实际使用的索引。如果是NULL,则没有使用索引。
查询中若是用覆盖索引,则该索引仅出现在key列表中。
在这里插入图片描述

7)key_len

表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好
key_len显示的值为索所字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的。

8)ref

显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。
在这里插入图片描述
9)rows【重要】

根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数。
在这里插入图片描述

10)Extra【重要】

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

① Using filesort【重要,要避免】

说明mysq|会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成的排序操作称为“文件排序”。
在这里插入图片描述
② Using temporary【重要,要避免】

使用临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于排序 order by 和分组查询 group by。
在这里插入图片描述
③ USING index【重要】

表示相应的select操作中使用了覆盖索引(Covering Index), 避免访问了表的数据行,效率不错!
如果同时出现usingwhere,表明索引被用来执行索引键值的查找。
如果没有同时出现usingwhere,表明索引用来读取数据而非执行查找动作。
在这里插入图片描述

覆盖索引(Covering Index)

覆盖索引(Covering Index) ,一说为索引覆盖。

理解方式一【推荐】:就是select的数据列只用从索引中就能够取得,不必读取数据行,MySQL可以利用索引返回select列表中的字段,而不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖。

理解方式二:索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据;当能通过读取索引就可以得到想要的数据,那就不需要读取行了。一个索引包含了(或覆盖了)满足查询结果的数据就叫做覆盖索引。

*注意:如果要使用覆盖索引,一定要注意select列表中只取出需要的列,不可select ,因为如果将所有字段一起做索引会导致索引文件过大,查询性能下降

④ Using where

表明是用来where过滤。

⑤ using join buffer

使用了连接缓存。

⑥ impossible where

where子句的值总是false,不能用来获取任何元组。
在这里插入图片描述

⑦ select tables optimized away

在没有 GROUPBY 子句的情况下,基于索引优化 MIN/MAX 操作或者对于 MyISAM 存储引擎优化 COUNT(*) 操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。

⑧ distinct

优化distinct操作,在找到第一匹配的元组后即停止找相同值得动作。

5、热身Case

在这里插入图片描述

第一行(执行顺序4) :id列为1,表示是union里的第一个select, select _type列的primary表示该查询为外层查询,table列被标记为<derived3>,表示查询结果来自一个衍生表,其中derived3中3代表该查询衍生自第三个select查询,即id为3的select。 【select d1.nam…】

第二行(执行顺序2):id为3,是整个查询中第三个select的一部分。因查询包含在from中,所以为derived。【 select id,name from t1 where other_column="】

第三行(执行顺序3):select列表 中的子查询select_type为subquery, 为整个查询中的第二个select。【select id from t3】

第四行(执行顺序1):select_type为union,说明第四个select是union里的第二个select,最先执【select name,id from t2】

第五行(执行顺序5):代表从union的临时表中读取行的阶段,table列的<union1,4> 表示用第一个和第四个select的结果进行union操作。【两个结果union操作】

5、索引优化

1、索引分析

1)单表

  • 建表SQL
CREATE TABLE IF NOT EXISTS `article`(
	`id` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
	`author_id` INT(10) UNSIGNED NOT NULL,
    `category_id` INT(10) UNSIGNED NOT NULL,
	`views` INT(10) UNSIGNED NOT NULL,
	`comments` INT(10) UNSIGNED NOT NULL,
	`title` VARBINARY(255) NOT NULL,
	`content` INT(10) 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'),
(3,3,3,3,'3','3');

SELECT * FROM article;
  • 案例
#查询category_id 为1 且comments大于1 的情况下,views最多的article_id。
EXPLAIN SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views LIMIT 1;

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

#开始优化:
#1.1 新建索引+删除索引
#ALTER TABLE `article` ADD INDEX idx_article_ccv(`category_id`,`comments`,`views`);
create index idx_article_ccv on article(category_id,comments,views);
show index from article;
DROP index idx_article_ccv ON article;

#1.2 第2次EXPLAIN
EXPLAIN SELECT id,author_id FROM article WHERE category_id = 1 AND comments >1 ORDER BY views LIMIT 1;
EXPLAIN SELECT id,author_id FROM article WHERE category_id = 1 AND comments =3 ORDER BY views LIMIT 1;
#结论:
#type 变成了range,这是可以忍受的。但是extra里使用Using filesort 仍是无法接受的。
#但是我们已经建立了索引,为啥没用呢?
#这是因为按照BTree索引的工作原理,先排序 category_id,
#如果遇到相同的category_id则再排序comments,如果遇到相同的comments则再排序views。
#当comments字段在联合索引里处于中间位置时,
#因comments> 1条件是一个范围值(所谓range),
#MySQL无法利用索引再对后面的views部分进行检索,即range类型查询字段后面的索引无效。.

#1.3 删除第一次建立的索引
DROP index idx_article_ccv ON article;

#1.4 第2次新建索引
#ALTER TABLE `article` ADD INDEX idx_article_ccv(`category_id`,`views`);
create index idx_article_ccv on article(category_id,views);

#1.5 第3次EXPLAIN
EXPLAIN SELECT id,author_id FROM article WHERE category_id = 1 AND comments >1 ORDER BY views LIMIT 1;
#结论:可以看到,type变成了ref,Extra中的Using filesort也消失了,结果非常理想。
DROP INDEX idx_article_ccv ON article;

2)两表

  • 建表SQL
CREATE TABLE IF NOT EXISTS `class`(
	`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
	`card` INT(10) UNSIGNED NOT NULL,
  PRIMARY KEY (`id`)
);

CREATE TABLE IF NOT EXISTS `book`(
	`bookid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
	`card` INT(10) UNSIGNED NOT NULL,
  PRIMARY KEY (`bookid`)
);

INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));

INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));

select * from class;
select * from book;
  • 案例
#下面开始explain分析
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
#结论: type 有All

#添加索引优化
ALTER TABLE `book` ADD INDEX Y (`card`);

#第2次explain
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
#可以看到第二行的type变为了ref,rows 也变成了优化比较明显。
#这是由左连接特性决定的。LEFT JOIN条件用于确定如何从右表搜索行,左边一定都有,
#所以右边是我们的关键点,一定需要建立索引。

#删除旧索引+新建+第3次explain
DROP INDEX Y ON book;
ALTER TABLE `class` ADD INDEX X (`card`);
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;

#然后来看一个右连接查询: 
#优化较明显。这是因为RIGHT JOIN条件用于确定如何从左表搜索行,右边一定都有,所以左边是我们的关键点,一定需要建立索引。
EXPLAIN SELECT * FROM class RIGHT JOIN book ON class.card = book.card;
DROP INDEX X ON class;
ALTER TABLE `class` ADD INDEX Y (`card`);
DROP INDEX Y ON class;

3)三表

  • 建表SQL
CREATE TABLE IF NOT EXISTS `phone`(
	`phoneid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
	`card` INT(10) UNSIGNED NOT NULL,
  PRIMARY KEY (`phoneid`)
)ENGINE=INNODB;

INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
  • 案例
#下面开始explain分析
EXPLAIN SELECT * FROM class INNER JOIN book ON class.card = book.card INNER JOIN phone on book.card = phone.card;
#结论: type 有All

#添加索引优化
ALTER TABLE `phone`ADD INDEX z(`card`);
ALTER TABLE `book`ADD INDEX Y(`card`);

EXPLAIN SELECT * FROM class INNER JOIN book ON class.card = book.card INNER JOIN phone on book.card = phone.card;
#后2行的type都是ref且 总rows优化很好,效果不错。因此索引最好设置在需要经常查询的字段中。

【结论】
Join语句的优化

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

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

1.建表SQL

CREATE TABLE staffs(
	id INT PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(24) NOT 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());

SELECT * FROM staffs;

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

2.案例(索引失效)

  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';

在这里插入图片描述

  1. 最佳左前缀法则

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

EXPLAIN SELECT * FROM staffs WHERE age=25 AND pos='dev';
EXPLAIN SELECT * FROM staffs WHERE pos='dev';

在这里插入图片描述
此时就创建的索引就没有用上,因为违背了左前缀法则。

  1. 不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描。
    在这里插入图片描述

  2. 存储引擎不能使用索引中范围条件右边的列
    在这里插入图片描述

  3. 尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select *
    在这里插入图片描述

  4. mysql在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描。
    在这里插入图片描述

  5. is null ,is not null也无法使用索引。
    在这里插入图片描述

  6. like以通配符开头(’%abc…’)mysq|索引失效会变成全表扫描的操作
    在这里插入图片描述
    问题:解决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,'[email protected]');
INSERT INTO tbl_user(NAME,age,email) VALUES('2aa2',222,'[email protected]');
INSERT INTO tbl_user(NAME,age,email) VALUES('3aa3',265,'[email protected]');
INSERT INTO tbl_user(NAME,age,email) VALUES('4aa4',21,'[email protected]');

#before INDEX

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

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

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

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);
  1. 字符串不加单引号索引失效
    在这里插入图片描述

  2. 少用or,用它来连接时会索引失效
    在这里插入图片描述

  3. 小总结

口诀:

  • 带头大哥不能死。
  • 中间兄弟不能断,永远要符合最佳最前缀原则。
  • 索引列上无计算。(计算:手动、自动、隐士的、显式的)
  • like %加右边。
  • 范围之后全失效。
  • 字符串里有引号。

在这里插入图片描述

3.面试题讲解
1)题目SQL

create table test03(
	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);
show index from test03;

#问题:我们创建了复合索引idx_test03_c1234,根据以下SQL分析下索引使用情况?
explain select * from test03 where c1='a1';
explain select * from test03 where c1='a1' and c2='a2';
explain select * from test03 where c1='a1' and c2='a2' and c3='a3';
explain select * from test03 where c1='a1' and c2='a2' and c3='a3' and c4='a4';

1)
explain select * from test03 where c1='a1' and c2='a2' and c3='a3' and c4='a4';
用到4个索引
2)
explain select * from test03 where c1='a1' and c2='a2' and c4='a4' and c3='a3';
用到4个索引
explain select * from test03 where c4='a4' and c3='a3' and c2='a2' and c1='a1';
用到4个索引
3)
explain select * from test03 where c1='a1' and c2='a2' and c3>'a3' and c4='a4';
用到3个索引
4)
explain select * from test03 where c1='a1' and c2='a2' and c4>'a4' and c3='a3';
用到4个索引
5)
explain select * from test03 where c1='a1' and c2='a2' and c4='a4' order by c3;
用到2个索引,c3作用在排序而不是查找
6)
explain select * from test03 where c1='a1' and c2='a2' order by c3;
用到2个索引
7)
explain select * from test03 where c1='a1' and c2='a2' order by c4;
用到2个索引,Using 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颠倒了
8.3
explain select * from test03 where c1='a1' and c2='a2' order by c2,c3;
用到2个索引
8.4
explain select * from test03 where c1='a1' and c2='a2' and c5='a5' order by c2,c3;
用c1、c2两个字段索引,但是c2,c3用于排序,无filesort
8.5
explain select * from test03 where c1='a1' and c2='a2' and c5='a5' order by c3,c2;
本例有常量c2的情况,和8.2对比
9)
explain select * from test03 where c1='a1' and c4='a4' group by c2,c3;
用到1个索引
10)
explain select * from test03 where c1='a1' and c4='a4' group by c3,c2;
用到1个索引,Using temporaryUsing filesort、Using where

结论:定值、范围还是排序,一般order by 是给个范围。
group by 基本上都需要进行排序,会有临时表产生。

3、一般性建议

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

如果有收获!!! 希望老铁们来个三连,点赞、收藏、转发。
创作不易,别忘点个赞,可以让更多的人看到这篇文章,顺便鼓励我写出更好的博客

猜你喜欢

转载自blog.csdn.net/weixin_45606067/article/details/107744169