MYSQL高级篇-----索引优化分析

2. 索引优化分析

2.1 原因

性能下降sql、执行时间长、等待时间长

  1. 查询语句写的差
  2. 索引失效 (索引建立了,没用上索引)

补充:索引分为单值和复合

  • 单值:单个表中的某个字段建一个索引
  • 复合:单个表中的某个字段建多个索引
    在这里插入图片描述

可以通过频繁使用给他建立索引,所以查询的比较快

  1. 关联查询太多join(设计缺陷或不得已的需求)
  2. 服务器调优及各个参数设置(缓冲、线程数等)

SQL执行顺序

select              # 5
	... 
from                # 1
	... 
where               # 2
	.... 
group by            # 3
	... 
having              # 4
	... 
order by            # 6
	... 
limit               # 7
	[offset]

在这里插入图片描述

2.2 常见通用的join查询

  • inner join 全连接
  • full outer join 外连接
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2KQ0m6Zg-1659805741431)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/71a3fbdfcb9c44c481218923913dae1e~tplv-k3u1fbpfcp-zoom-1.image)]

具体其sql代码如下:

执行sql建表

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`)
)ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8;

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 CHARACTER SET = utf8;



INSERT INTO `atguigudb`.`tbl_dept`(`id`, `deptName`, `locAdd`) VALUES (1, '11', '111');
INSERT INTO `atguigudb`.`tbl_dept`(`id`, `deptName`, `locAdd`) VALUES (11, '11', '111');
INSERT INTO `atguigudb`.`tbl_dept`(`id`, `deptName`, `locAdd`) VALUES (2, '11', '111');
INSERT INTO `atguigudb`.`tbl_dept`(`id`, `deptName`, `locAdd`) VALUES (4, '11', '111');
INSERT INTO `atguigudb`.`tbl_dept`(`id`, `deptName`, `locAdd`) VALUES (5, '11', '111');

INSERT INTO `atguigudb`.`tbl_emp`(`id`, `name`, `deptId`) VALUES (1, '11', 1);
INSERT INTO `atguigudb`.`tbl_emp`(`id`, `name`, `deptId`) VALUES (2, '11', 11);
INSERT INTO `atguigudb`.`tbl_emp`(`id`, `name`, `deptId`) VALUES (3, '11', 2);
INSERT INTO `atguigudb`.`tbl_emp`(`id`, `name`, `deptId`) VALUES (4, '11', 3);
INSERT INTO `atguigudb`.`tbl_emp`(`id`, `name`, `deptId`) VALUES (5, '11', 4);



实例代码

/* 1 */
SELECT <select_list> FROM TableA A LEFT JOIN TableB B ON A.Key = B.Key;

/* 2 */
SELECT <select_list> FROM TableA A RIGHT JOIN TableB B ON A.Key = B.Key;

/* 3 */
SELECT <select_list> FROM TableA A INNER JOIN TableB B ON A.Key = B.Key;

/* 4 */
SELECT <select_list> FROM TableA A LEFT JOIN TableB B ON A.Key = B.Key WHERE B.Key IS NULL;

/* 5 */
SELECT <select_list> FROM TableA A RIGHT JOIN TableB B ON A.Key = B.Key WHERE A.Key IS NULL;

/* 6 */
SELECT <select_list> FROM TableA A FULL OUTER JOIN TableB B ON A.Key = B.Key;
/* MySQL不支持FULL OUTER JOIN这种语法 可以改成 1+2 */
SELECT <select_list> FROM TableA A LEFT JOIN TableB B ON A.Key = B.Key
UNION
SELECT <select_list> FROM TableA A RIGHT JOIN TableB B ON A.Key = B.Key;

/* 7 */
SELECT <select_list> FROM TableA A FULL OUTER JOIN TableB B ON A.Key = B.Key WHERE A.Key IS NULL OR B.Key IS NULL;
/* MySQL不支持FULL OUTER JOIN这种语法 可以改成 4+5 */
SELECT <select_list> FROM TableA A LEFT JOIN TableB B ON A.Key = B.Key WHERE B.Key IS NULL;
UNION
SELECT <select_list> FROM TableA A RIGHT JOIN TableB B ON A.Key = B.Key WHERE A.Key IS NULL;

上图中第6个的实现 可以通过如下:

由于有些mysql不能使用full join,不过可以换种方法表示
A 的独有 + AB 共有 + B的独有
union本身就可以去重
所以可以这样使用

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个的实现可以通过如下:
也就是A的独有+ B的独有
之后通过union进行合并

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.2-5.7

2.3 索引

提高效率,类比资源
排好序的、快速查找(影响order by)数据结构

从而可以获得索引的本质:索引是排好序的快速查找数据结构。

在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引

重点:索引会影响到MySQL查找(WHERE的查询条件)和排序(ORDER BY)两大功能!

索引的目的在于提高查询效率,可以类比字典的目录。如果要查mysql这个这个单词,我们肯定要先定位到m字母,然后从上往下找y字母,再找剩下的sql。如果没有索引,那么可能需要a—z,这样全字典扫描,如果我想找Java开头的单词呢?如果我想找Oracle开头的单词呢?

官方解释:

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

如下方二叉树的数据结构所示
索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cs9UcEnb-1659805741434)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/942f09dfaf444d1cbef886cae61a63ff~tplv-k3u1fbpfcp-zoom-1.image)]

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

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

# Linux下查看磁盘空间命令 df -h 
[root@Ringo ~]# df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        40G   16G   23G  41% /
devtmpfs        911M     0  911M   0% /dev
tmpfs           920M     0  920M   0% /dev/shm
tmpfs           920M  480K  920M   1% /run
tmpfs           920M     0  920M   0% /sys/fs/cgroup
overlay          40G   16G   23G  41% 
  • 平常所说的索引,如果没有特别指明,都是指B树(多路搜索树,并不一定是二叉的)结构组织的索引。其中聚集索引,次要索引,覆盖索引,复合索引,前缀索引
  • 唯一索引默认都是使用B+树索引,统称索引。

当然,除了B+树这种类型的索引之外,还有哈稀索引(hash index)等。

优势:

  • 提高数据检索的效率,降低数据库的IO成本(不用一直通过磁盘查找)
  • 通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗

劣势

  • 索引列也是要占用空间的(占空间)
  • 更新表时,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息

MysQL有大数据量的表,需要花时间研究建立最优秀的索引,或优化查询

2.3.1 索引分类(重点)

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

建议:一张表建的索引最好不要超过5个!

基本语法:

INDEX :索引关键字
indexName :索引名字(自己起的一般表名+字段名)
mytable:表名
columnName:字段名

  • 创建
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 tableName;
/* 基本语法 */

/* 1、创建索引 [UNIQUE]可以省略*/
/* 如果只写一个字段就是单值索引,写多个字段就是复合索引 */
CREATE [UNIQUE] INDEX indexName ON tabName(columnName(length));

/* 2、删除索引 */
DROP INDEX [indexName] ON tabName;

/* 3、查看索引 */
/* 加上\G就可以以列的形式查看了 不加\G就是以表的形式查看 */
SHOW INDEX FROM tabName \G;

添加具体有四种方式:

  1. ALTER TABLE tbl_name ADD PRIMARY KEY (column_list);:该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL。
  2. ALTER TABLE tbl_name ADD UNIQUE index_name (column_list);:这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)。
  3. ALTER TABLE tbl_name ADD INDEX index_name (column_list);:添加普通索引,索引值可出现多次。
  4. ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list);:该语句指定了索引为FULLTEXT,用于全文索引。
    在这里插入图片描述

2.3.2 索引结构

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

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

初始化介绍

浅蓝色的称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示)
如磁盘块1包含数据项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指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,
29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。

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

2.3.3 索引情况(那些需要那些不需要)

分为有索引和无索引
索引(查找,排序)

需要建立索引的情况有:
视频地址

  • 主键自动建立唯一索引
  • 频繁作为查询条件的字段应该创建索引
  • 查询中与其它表关联的字段,外键关系建立索引
  • 单键/组合索引的选择问题,who?(在高并发下倾向创建组合索引)
  • 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
  • 查询中统计或者分组字段(分组都是需要排序的)

不需要简历索引的情况有:

  • 表记录太少
  • 经常增删改的表(提高了查询速度,但是会同时江低更新表的速度,对表进行更新的时候,mysql还要保存数据,还要保存一下索引文件)
  • 数据重复且分布平均的表字段,因此应该只为最经常查询和最经常排序的数据列建立索引。注意,如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果。
  • 频繁更新的字段不适合创建索引,因为每次更新不单单是更新了记录还会更新索引
  • Where条件里用不到的字段不创建索引

假如一个表有10万行记录,有一个字段A只有true和false两种值,并且每个值的分布概率大约为50%,那么对A字段建索引一般不会提高数据库的查询速度。索引的选择性是指索引列中不同值的数目与表中记录数的比。如果一个表中有2000条记录,表索引列有1980个不同的值,那么这个索引的选择性就是1980/2000=0.99。一个索引的选择性越接近于1,这个索引的效率就越高。

一个索引的选择性越接近于1,这个索引的效率就越高

2.4 性能分析

MySQL Query Optimizer:Mysql中有专门负责优化SELECT语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的Query提供他认为最优的执行计划,但不见得最优
在这里插入图片描述

MySQL常见瓶颈(通过cpu 、io 、 服务器的硬件进行分析)

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

EXPLAIN是什么?

EXPLAIN:SQL的执行计划,使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理SQL语句的。

EXPLAIN怎么使用?

语法:explain + SQL

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e0M2r1Be-1659805741436)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/24e19b34128649febf61518ddd8210bd~tplv-k3u1fbpfcp-zoom-1.image)]

关于explain的功能可以

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

2.4.1 id(查询序列号)

视频地址

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

三种情况:

  • id相同,执行顺序由上至下
    (id都是1,执行顺序从上到下)\

在这里插入图片描述

  • id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
    (在内层的子查询序列,其等级越高)\

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nxHUSiWc-1659874478155)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3b7df05f3e3c47a19712b15b495cbee0~tplv-k3u1fbpfcp-zoom-1.image)]

  • (以上两种情况同时存在的时候)
    注意框框是一个临时表
    按照上面的规则进行排序
    id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行,

衍生=DERIVED\
在这里插入图片描述

2.4.2 select_type(查询类型)

select_type:查询的类型,主要是用于区别普通查询、联合查询、子查询等的复杂查询。
在这里插入图片描述

在这里插入图片描述

具体的类型有:

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

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

  • SUBQUERY - 在SELECT或WHERE列表中包含了子查询。(内层)越内层等级越高,越先执行\

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

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

  • UNION RESULT - 从UNION表获取结果的SELECT。(合并之后的查询就是这个选项)

table

table:表名。

2.4.3 type(访问类型)

访问类型排列

type显示的是访问类型,是较为重要的一个指标,结果值从最好到最坏依次是:(较全的,只做参考,熟悉下面的即可)

system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index >ALL

常见的指标主要有: (最好>最差)由好到------坏;

system>const>eq_ref>ref>range>index>ALL

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

  • system:表只有一行记录(等于系统表),这是const类型的特列,平时不会出现,这个也可以忽略不计。

  • const:表示通过索引一次就找到了,const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快如将主键置于where列表中,MySQL就能将该查询转换为一个常量。(单表中的主键id,一张表一个条件
    在这里插入图片描述

  • eq_ref唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫(联表唯一,和上面的区别在于索引数量不同
    在这里插入图片描述

  • ref:非唯一性索引扫描,返回匹配某个单独值的所有行,本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体。(上面的条件是一对一,这个条件是一对多
    在这里插入图片描述

  • range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引一般就是在你的where语句中出现了between、<、>、in等的查询。这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束语另一点,不用扫描全部索引。

  • index:Full Index Scan,index与ALL区别为index类型只遍历索引树。这通常比ALL快,因为索引文件通常比数据文件小(也就是说虽然all和Index都是读全表,但index是从索引中读取的,而all是从硬盘中读的)。

在这里插入图片描述

  • all:Full Table Scan,将遍历全表以找到匹配的行。
    (也就是通过 select * 全部数据读取)

2.4.4 possible_keys 、key 和 key_len(可能用到索引、实际用到索引、长度)

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

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

所谓的覆盖索引:查询时未发生回表。查询的字段只能建立在索引的字段中

若存在索引无使用那么称之为 索引失效;
在这里插入图片描述
在这里插入图片描述
视频讲解

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

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

总结:同样的结果key_len 越小越好;
在这里插入图片描述
(demo中一个where 是13 2个是26 )

2.4.5 ref(条件查询)

(显示使用到的条件查询,如果是常量就为const)
显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5vLxhu7q-1659805741446)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/708b4e26772d4a1b981cfa46ef4e7461~tplv-k3u1fbpfcp-zoom-1.image)]

由key_len可知t1表的idx_col1_col2被充分使用,col1匹配t2表的col1,col2匹配了一个常量,即 ‘ac’。
查询中与其它表关联的字段,外键关系建立索引。
在这里插入图片描述

2.4.6 rows(行数)

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

每张表被优化器查询
把不合适的索引删除,慢慢优化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HeLDhB8n-1659805741446)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3c185df731bb49559fb1ef75087f6f38~tplv-k3u1fbpfcp-zoom-1.image)]
在加了索引之后 rows 少了很多;rows越小越好;(查询的条数就小)

2.4.7 explain例子分析

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

Using filesort:说明MySQL会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成的排序操作成为"文件内排序"。
排序没有使用索引

mysql> explain select name from pms_category where name='Tangs' order by cat_level \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: pms_category
   partitions: NULL
         type: ref
possible_keys: idx_name_parentCid_catLevel
          key: idx_name_parentCid_catLevel
      key_len: 201
          ref: const
         rows: 1
     filtered: 100.00
        Extra: Using where; Using index; Using filesort
1 row in set, 1 warning (0.00 sec)

#~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
# 排序使用到了索引

mysql> explain select name from pms_category where name='Tangs' order by parent_cid,cat_level\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: pms_category
   partitions: NULL
         type: ref
possible_keys: idx_name_parentCid_catLevel
          key: idx_name_parentCid_catLevel
      key_len: 201
          ref: const
         rows: 1
     filtered: 100.00
        Extra: Using where; Using index
1 row in set, 1 warning (0.00 sec)

在这里插入图片描述

Using temporary:使用了临时表保存中间结果,MySQL在対查询结果排序时使用了临时表。常见于排序order by和分组查询group by。临时表対系统性能损耗很大。

在这里插入图片描述

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

覆盖索引

就是select的数据列只用从索引中就能够取得,不必从数据表中读取,换句话说查询列要被所使用的索引覆盖。
在这里插入图片描述
在这里插入图片描述

*注意:如果要使用覆盖索引,一定不能写SELECT ,要写出具体的字段。

mysql> explain select cat_id from pms_category \G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: pms_category
   partitions: NULL
         type: index
possible_keys: NULL       
          key: PRIMARY
      key_len: 8
          ref: NULL
         rows: 1425
     filtered: 100.00
        Extra: Using index   # select的数据列只用从索引中就能够取得,不必从数据表中读取   
1 row in set, 1 warning (0.00 sec)

Using where:表明使用了WHERE过滤。
Using join buffer:使用了连接缓存。(join (使用多的话)过程中不断夸大 ,可多设置写连接缓冲)
impossible where:WHERE子句的值总是false,不能用来获取任何元组。
在这里插入图片描述
可以理解为一个人的名字 等于 王五 又等于 老刘 冲突且结果为false 则会现在impossible where;

mysql> explain select name from pms_category where name = 'zs' and name = 'ls'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: NULL
   partitions: NULL
         type: NULL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: NULL
     filtered: NULL
        Extra: Impossible WHERE   # 不可能字段同时查到两个名字
1 row in set, 1 warning (0.00 sec)

在这里插入图片描述

扩展:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s4iq8h7v-1659805741447)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/578a5a70f30646c8bfcf32b16a4f22f9~tplv-k3u1fbpfcp-zoom-1.image)]
ID越大越先执行;

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

第二行(执行顺序2):id为3,是整个查询中第三个select的一部分。因查询包含在from中,所以为derived。【select id,namefrom 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操作】

2.5 优化分析

2.5.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 from 表名;

4、查看现在SQL语句的执行计划。

在这里插入图片描述
我们发现,创建符合索引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的执行计划。

在这里插入图片描述

推论:当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  from  表名;

在这里插入图片描述

7、当前索引是idx_article_cv,来看一下SQL执行计划。

explain

2.5.2 索引两表优化

建立两张表

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

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

1、不创建索引的情况下,SQL的执行计划。

explain

book和class两张表都是没有使用索引,全表扫描,那么如果进行优化,索引是创建在book表还是创建在class表呢?下面进行大胆的尝试!

2、左表(book表)创建索引。

创建索引idx_book_card

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

在book表中有idx_book_card索引的情况下,查看SQL执行计划
在这里插入图片描述

3、删除book表的索引,右表(class表)创建索引。

创建索引idx_class_card

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

在class表中有idx_class_card索引的情况下,查看SQL执行计划

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

由此可见,左连接将索引创建在右表上更合适,右连接将索引创建在左表上更合适
所以:索引两表优化,左连接右表建索引,右连接左表建索引

2.5.3 索引三表优化

建立三张表:
(在前面中有两张表,现在多一个表即可)

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

三表连接查询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的执行计划
、

2.5.4 Join语句的优化

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

2.6 索引失效

常见情况:

  1. 全值匹配我最爱。
  2. 最佳左前缀法则。
  3. 不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描。
  4. 索引中范围条件右边的字段会全部失效。
  5. 尽量使用覆盖索引(只访问索引的查询,索引列和查询列一致),减少SELECT *。
  6. MySQL在使用!=或者<>的时候无法使用索引会导致全表扫描。
  7. is null、is not null也无法使用索引。
  8. like以通配符开头%abc索引失效会变成全表扫描(使用覆盖索引就不会全表扫描了)。
  9. 字符串不加单引号索引失效。
  10. 少用or,用它来连接时会索引失效。

为了更好的演示效果,采用实战方式

建立一张表

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());

建立索引 ALTER TABLE staffs ADD INDEX index_staffs_nameAgePos(name,age,pos);

全值匹配,且从左到右,只不过长度变长了
在这里插入图片描述

/* 用到了idx_staffs_name_age_pos索引中的name字段 */
EXPLAIN SELECT * FROM `staffs` WHERE `name` = 'Ringo';

/* 用到了idx_staffs_name_age_pos索引中的name, age字段 */
EXPLAIN SELECT * FROM `staffs` WHERE `name` = 'Ringo' AND `age` = 18;

/* 用到了idx_staffs_name_age_pos索引中的name,age,pos字段 这是属于全值匹配的情况!!!*/
EXPLAIN SELECT * FROM `staffs` WHERE `name` = 'Ringo' AND `age` = 18 AND `pos` = 'manager';

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

索引失效(应该避免)

从左开始且不跳列才不会失效

  • 最佳左前缀法则 - 如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过复合索引中间列。
/* 索引没用上,ALL全表扫描 */
EXPLAIN SELECT * FROM `staffs` WHERE `age` = 18 AND `pos` = 'manager';

/* 索引没用上,ALL全表扫描 */
EXPLAIN SELECT * FROM `staffs` WHERE `pos` = 'manager';

/* 用到了idx_staffs_name_age_pos索引中的name字段,pos字段索引失效 */
EXPLAIN SELECT * FROM `staffs` WHERE `name` = 'Ringo' AND `pos` = 'manager';

在这里插入图片描述

在这里插入图片描述

索引列上不做额外操作才不会失效

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

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

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

范围之后全失效

/* 用到了idx_staffs_name_age_pos索引中的name,age,pos字段 这是属于全值匹配的情况!!!*/
EXPLAIN SELECT * FROM `staffs` WHERE `name` = 'Ringo' AND `age` = 18 AND `pos` = 'manager';


/* 用到了idx_staffs_name_age_pos索引中的name,age字段,pos字段索引失效 */
EXPLAIN SELECT * FROM `staffs` WHERE `name` = '张三' AND `age` > 18 AND `pos` = 'dev';

在这里插入图片描述

少用>,<,between…and等结构

  • 存储引擎不能使用索引中范围条件右边的列。(就是>,<,between…and)*

减少select *

  • 尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致))
    区别在于extra,索引的不同,速度不一样\

*在写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';

在这里插入图片描述

不用!=或者<>

不等有时会失效

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

在这里插入图片描述

不用 is null, is not null

  • is null, is not null 也无法使用索引。\

在这里插入图片描述

模糊查询

  • like以通配符开头(’%abc…’),mysql索引失效会变成全表扫描的操作。

扩展:

详细理解可查看 博客11节


#like 模糊查询 查询 相似值
#%% 代表不确定个数的字符  代表前后有若干个值可能是0 ,1,或者其他
#查询name中包含 a的值
SELECT department_id,last_name,salary
FROM employees
WHERE last_name LIKE '%a%';



#查询name中 a 开头的值
SELECT department_id,last_name,salary
FROM employees
WHERE last_name LIKE 'a%';

#查询name中 a 结尾的值
SELECT department_id,last_name,salary
FROM employees
WHERE last_name LIKE '%a';

#查询包含a e的值
#写法1 
SELECT department_id,last_name,salary
FROM employees
WHERE last_name LIKE '%a%' AND last_name LIKE '%e%';

#写法2 
SELECT department_id,last_name,salary
FROM employees
WHERE last_name LIKE '%a%e%' or last_name LIKE '%e%a%';

#查询第二个字符是a的值
#_ 代表不确定的字符
SELECT department_id,last_name,salary
FROM employees
WHERE last_name LIKE '_a%'

在这里插入图片描述

注意看上面模糊查询的细节,只有xx%前缀查询才不会失效

那如何改变%xx% 让其不失效呢\

引入一张表

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;

INSERT INTO tbl_user(`name`,`age`,`email`)VALUES('1aa1',21,'[email protected]');
INSERT INTO tbl_user(`name`,`age`,`email`)VALUES('2bb2',23,'[email protected]');
INSERT INTO tbl_user(`name`,`age`,`email`)VALUES('3cc3',24,'[email protected]');
INSERT INTO tbl_user(`name`,`age`,`email`)VALUES('4dd4',26,'[email protected]');

在没有索引的时候,怎么查询都是全表查询
增加一个索引

覆盖索引
全部覆盖

CREATE INDEX idx_user_nameAge ON tbl_user(NAME,age);

之后的索引查询为字段值\

视频地址

如果使用下面这些,都是使用的覆盖索引,结果都是一样的

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

id之所以没加索引,但是可以加入使用不会失效,是因为他是主键

但是如果加入了没有主键又不是索引的东西,%xx%就会失效\

/* 使用到了覆盖索引 */
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';

在这里插入图片描述

类型要正确
即使类型不正确也可以查询,但是底层会帮你转换类型,在判断,但会浪费时间,索引直接失效,变成了全表查询

  • 字符串不加单引号索引失效。\

在这里插入图片描述

不用or关键字就不会失效

  • 少用or,用它来连接时会索引失效。
    在这里插入图片描述
    口诀:覆盖索引保两边。

视频地址

2.7 面试常考

为了更加贴切的展示,先建立一张表

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);
在这里插入图片描述

以下都符合最左前缀原则,都会用到索引,只是估计用到的长度在变化而已

正常顺序

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

乱序
即使更改一下顺序,mysql的最左前缀原则还是符合的,因为mysql有优化器会帮你查询是否匹配

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

在这里插入图片描述

限定范围
如果开始限定范围
第一条会用到3个索引,前两个用来查找,第三个用来排序
第二条用到了4个索引,前3个用来查找,第四个用来排序

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

通过看其长度也可知道

范围之后(索引)全失效:在本文当中当我们使用c4的时候 c4后面已经没有查询了,但如果是c3的话 c3后面存在c4的;(可理解为在创建索引中的顺序为准;c4是最后的他后面失效没用了他后面就无数据)
在这里插入图片描述

使用order by
下面那个例子都是一样的
前两个都是在查找,第三个只是在排序,到了这里就已经断了,所以第四个可有可无

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

在这里插入图片描述

如果把3换成4,会出现Using filesort。,因为优化器会给你文件排序(因为中间跳了一个)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zc7GaexN-1659805741463)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6e6b2a38b4d94e2dbb88f2827639bd47~tplv-k3u1fbpfcp-zoom-1.image)]](https://img-blog.csdnimg.cn/4249633b94cd45e98304c58c5c40627f.png)

如果使用这个不会出现Using filesort,因为是正常的索引顺序在这里插入图片描述

而这个就会出现Using filesort,因为顺序颠倒了

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

下面这两个一样的功能
前两个是查找,第二三是排序

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

这个就不一样,如果确定了一个查找的顺序之后,即使排序乱序也不会出现filesort
但是如果不用的话就会出现

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;

![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-agY637dQ-1659805741464)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7809f38b02e64f2eacb4151d48109127~tplv-k3u1fbpfcp-zoom-1.image)]](https://img-blog.csdnimg.cn/989e92e35e7e448eb7fee4cc9fc7751b.png)

group by

如果乱序,还会多出现一个临时表

定值、范围还是排序,一般order by是给个范围

group by基本上都需要进行排序,会有临时表产生

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

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

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

模糊查询

explain select * from test03 where c1='a1' and c2 like 'kk%' and c3='a3';
explain select * from test03 where c1='a1' and c2 like '%kk' and c3='a3';
explain select * from test03 where c1='a1' and c2 like '%kk%' and c3='a3';
explain select * from test03 where c1='a1' and c2 like 'k%kk%' and c3='a3';

具体其执行结果如下
在这里插入图片描述

整合:

/* 最好索引怎么创建的,就怎么用,按照顺序使用,避免让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`;

2.8 总结

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

假设index(a, b, c)

where语句 索引是否被使用
where a = 3 可以,使用到a
where a = 3 and b = 5 可以,使用到a,b
where a = 3 and b = 5 and c = 4 可以,使用到a,b,c
where b = 3 或者 where b = 3 and c = 4 或者 where c = 4 不可
where a = 3 and c = 5 使用到a,但是c不可以,b中间断了
where a = 3 and b > 4 and c = 5 使用到a和b,c不能用在范围之后,b断了
where a = 3 and b like ‘kk%’ and c = 4 可以,使用到a,b,c。
where a = 3 and b like ‘%kk’ and c = 4 Y,使用到a
where a = 3 and b like ‘%kk%’ and c = 4 Y,使用到a
where a= 3 and b like ‘k%kk%’ and c = 4 Y,使用到a,b,c

在这里插入图片描述

优化的口诀如下:

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

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

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

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

不等空值还有 OR, 索引影响要注意;

VAR 引号不可丢, SQL 优化有诀窍。

猜你喜欢

转载自blog.csdn.net/qq_42055933/article/details/126204118