尚硅谷MySQL数据库高级-02-索引优化分析

本文深入探讨了MySQL索引的优化分析,包括性能下降的原因、SQL语句编写、索引失效场景及解决策略。通过实例展示了如何利用索引提升JOIN查询效率,分析了索引的工作原理与优劣,以及如何根据执行计划调整索引策略,特别是在应对复杂查询和排序时的考虑。还介绍了如何合理创建和选择索引类型,如单值、复合、唯一索引等,以平衡查询效率与数据更新成本。

转载参考:

  • https://github.com/RingoTangs/LearningNote/blob/master/MySQL/MySQL.md
  • https://blog.youkuaiyun.com/qq_21579045/article/details/99702766

索引优化分析

性能下降SQL慢

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

SQL执行顺序

  • 手写
    在这里插入图片描述

  • 机读
    在这里插入图片描述

  • 总结
    在这里插入图片描述

常见通用的Join查询

在这里插入图片描述

SELECT * from tbl_emp a left join tbl_dept b on a.deptId = b.id;

SELECT * from tbl_emp a right join tbl_dept b on a.deptId = b.id;


SELECT * from tbl_emp a inner join tbl_dept b on a.deptId = b.id;


SELECT * from tbl_emp a left join tbl_dept b on a.deptId = b.id where b.id is null;

SELECT * from tbl_emp a right join tbl_dept b on a.deptId = b.id where a.id is null;


/* MySQL不支持FULL OUTER JOIN这种语法 可以改成 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;


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.id is null;

在这里插入图片描述

UnionUnion All区别:
* Union:对两个结果集进行并集操作,不包括重复行,同时进行默认规则的排序;
* Union All:对两个结果集进行并集操作,包括重复行,不进行排序;

索引简介

是什么

  • MySQL官方对索引的定义为:索引(INDEX)是帮助MySQL高效获取数据的数据结果。从而可以获得索引的本质:索引是排好序的快速查找数据结构。
  • 索引的目的在于提高查询效率,可以类比字典的目录。如果要查mysql这个这个单词,我们肯定要先定位到m字母,然后从上往下找y字母,再找剩下的sql。如果没有索引,那么可能需要a—z,这样全字典扫描,如果我想找Java开头的单词呢?如果我想找Oracle开头的单词呢???
  • 重点:索引会影响到MySQL查找(WHERE的查询条件)和排序(ORDER BY)两大功能!
  • 除了数据本身之外,数据库还维护着一个满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构的基础上实现高级查找算法,这种数据结构就是索引。

在这里插入图片描述

  • 一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上。
  • 我们平时所说的索引,如果没有特别指明,都是指B树(多路搜索树,并不一定是二叉的)结构组织的索引。其中聚集索引,次要索引,覆盖索引,复合索引,前缀索引,唯一索引默认都是使用B+树索引,统称索引。当然,除了B+树这种数据结构的索引之外,还有哈希索引(Hash Index)等。
  • 一般企业数据是伪删除,一方面大数据,一方面是破坏索引

索引的优势和劣势

  • 优势:
    • 查找:类似大学图书馆的书目索引,提高数据检索的效率,降低数据库的IO成本。
    • 排序:通过索引対数据进行排序,降低数据排序的成本,降低了CPU的消耗。
  • 劣势:
    • 实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的。
    • 虽然索引大大提高了查询速度,但是同时会降低表的更新速度,例如对表频繁的进行INSERT、UPDATE和DELETE。因为更新表的时候,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加的索引列的字段,都会调整因为更新所带来的键值变化后的索引信息。
    • 索引只是提高效率的一个因素,如果MySQL有大数据量的表,就需要花时间研究建立最优秀的索引。

索引分类

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

命令语句

  • 创建:

    • create [unique] index indexname on mytable(columnname(length));

    • alter mytable add [unique] index [indexname] on (columnname(length))

    • 如果是char,varchar类型,length可以小于字段实际长度;如果是blob和text类型,必须指定length。

  • 删除:drop index [indexname] on mytable;

  • 查看:show index from table_name\G

  • 使用alter命令

/* 1、该语句添加一个主键,这意味着索引值必须是唯一的,并且不能为NULL */
ALTER TABLE tabName ADD PRIMARY KEY(column_list);

/* 2、该语句创建索引的键值必须是唯一的(除了NULL之外,NULL可能会出现多次) */
ALTER TABLE tabName ADD UNIQUE indexName(column_list);

/* 3、该语句创建普通索引,索引值可以出现多次 */
ALTER TABLE tabName ADD INDEX indexName(column_list);

/* 4、该语句指定了索引为FULLTEXT,用于全文检索 */
ALTER TABLE tabName ADD FULLTEXT indexName(column_list);

索引结构

  • BTree索引。

    • 索引原理
      在这里插入图片描述
      在这里插入图片描述
  • Hash索引。

  • Full-text全文索引。

  • R-Tree索引。

哪些情况需要建索引

  • 主键自动建立主键索引(唯一 + 非空)。
  • 频繁作为查询条件的字段应该创建索引。
  • 查询中与其他表关联的字段,外键关系建立索引。
  • 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度。
  • 查询中统计或者分组字段(group by也和索引有关)。

哪些情况不要建索引

  • 表记录太少
  • 经常增删改的表
    • Why:提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件。
  • 频繁更新的字段不适合创建索引
    • 因为每次更新不单单是更新了记录,还会更新索引,加重IO负担
  • where条件里用不到的字段不创建索引
  • 单键/组合索引的选择问题。
    • who?(在高并发下倾向创建组合索引)
  • 数据重复且分布平均的表字段,因此应该只为最经常查询和最经常排序的数据列建立索引。注意,如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果。
    在这里插入图片描述

性能分析

MySQL Query Optimizer

在这里插入图片描述

MySQL常见瓶颈

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

Explain

是什么(查看执行计划)

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

能干嘛

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

怎么玩

explain + SQL

在这里插入图片描述

各字段解释

id
  • id:表的读取和加载顺序。

  • 值有以下三种情况:

    • id相同,执行顺序由上至下。
    • id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行。
    • id相同不同,同时存在。永远是id大的优先级最高,id相等的时候顺序执行。
  • 衍生:DERIVED

在这里插入图片描述

select_type
  • 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。
table
  • 显示这一行的数据是关于哪些表的。
type
  • 访问类型排列

  • 从最好到最差依次是:system>const>eq_ref>ref>range>index>ALL。除了ALL没有用到索引,其他级别都用到索引了。

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

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

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

  • eq_ref:唯一性索引扫描,读取本表中和关联表表中的每行组合成的一行,查出来只有一条记录。除 了 system 和 const 类型之外, 这是最好的联接类型。

  • ref:非唯一性索引扫描,返回本表和关联表某个值匹配的所有行,查出来有多条记录。

  • range:只检索给定范围的行,一般就是在WHERE语句中出现了BETWEEN、< >、in等的查询。这种范围扫描索引比全表扫描要好,因为它只需要开始于索引树的某一点,而结束于另一点,不用扫描全部索引。

  • index:Full Index Scan,全索引扫描,index和ALL的区别为index类型只遍历索引树。也就是说虽然ALL和index都是读全表,但是index是从索引中读的,ALL是从磁盘中读取的。

  • ALL:Full Table Scan,没有用到索引,全表扫描。

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

possible_keys
  • 显示可能应用在这张表中的索引,一个或者多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。
key
  • 实际使用的索引。如果为NULL,则没有使用索引。
  • 查询中如果使用了覆盖索引,则该索引仅仅出现在key列表中。(覆盖索引:查询的字段与建立的复合索引的个数一一吻合)

在这里插入图片描述

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,表明索引用来读取数据而非执行查找动作。

在这里插入图片描述

在这里插入图片描述

  • Using where:表明使用了where过滤。

  • Using join buffer:使用了连接缓存。

  • impossible where:where子句的值总是false,不能用来获取任何元组。(查询语句中where的条件不可能被满足,恒为False)

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

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

最左原则,按楼梯走。跳楼梯就不能Using index。

case

c

索引优化

索引单表优化案例

  • 编写SQL语句并查看SQL执行计划。
mysql>  SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
+----+-----------+
| id | author_id |
+----+-----------+
|  5 |         1 |
+----+-----------+
1 row in set (0.00 sec)

mysql> explain SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                       |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
|  1 | SIMPLE      | article | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    5 |    20.00 | Using where; Using filesort |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
1 row in set, 1 warning (0.00 sec)

  • 创建和查看索引idx_article_ccv
mysql> create index idx_article_ccv on article(category_id,comments,views);
Query OK, 0 rows affected (0.25 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> show index from article;
+---------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table   | Non_unique | Key_name        | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+---------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| article |          0 | PRIMARY         |            1 | id          | A         |           5 |     NULL | NULL   |      | BTREE      |         |               |
| article |          1 | idx_article_ccv |            1 | category_id | A         |           3 |     NULL | NULL   |      | BTREE      |         |               |
| article |          1 | idx_article_ccv |            2 | comments    | A         |           5 |     NULL | NULL   |      | BTREE      |         |               |
| article |          1 | idx_article_ccv |            3 | views       | A         |           5 |     NULL | NULL   |      | BTREE      |         |               |
+---------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
4 rows in set (0.04 sec)

  • 查看现在SQL语句的执行计划
    • 我们发现,创建符合索引idx_article_ccv之后,虽然解决了全表扫描的问题,但是Extra居然还是用的Using filesort,为什么?
mysql> explain SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
+----+-------------+---------+------------+-------+-----------------+-----------------+---------+------+------+----------+---------------------------------------+
| id | select_type | table   | partitions | type  | possible_keys   | key             | key_len | ref  | rows | filtered | Extra                                 |
+----+-------------+---------+------------+-------+-----------------+-----------------+---------+------+------+----------+---------------------------------------+
|  1 | SIMPLE      | article | NULL       | range | idx_article_ccv | idx_article_ccv | 8       | NULL |    2 |   100.00 | Using index condition; Using filesort |
+----+-------------+---------+------------+-------+-----------------+-----------------+---------+------+------+----------+---------------------------------------+
1 row in set, 1 warning (0.00 sec)

  • 因为type是range ,决定尝试把修改非range
    • 推论:当comments > 1的时候order by排序views字段索引就用不上,但是当comments = 1的时候order by排序views字段索引就可以用上!!!所以,范围之后的索引会失效。
mysql> explain SELECT id,author_id FROM article WHERE category_id = 1 AND comments = 1 ORDER BY views DESC LIMIT 1;
+----+-------------+---------+------------+------+-----------------+-----------------+---------+-------------+------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys   | key             | key_len | ref         | rows | filtered | Extra       |
+----+-------------+---------+------------+------+-----------------+-----------------+---------+-------------+------+----------+-------------+
|  1 | SIMPLE      | article | NULL       | ref  | idx_article_ccv | idx_article_ccv | 8       | const,const |    1 |   100.00 | Using where |
+----+-------------+---------+------------+------+-----------------+-----------------+---------+-------------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

  • 我们现在知道范围之后的索引会失效,原来的索引idx_article_ccv最后一个字段views会失效,那么我们如果删除这个范围字段的索引,创建新的索引idx_article_cv
mysql> drop index idx_article_ccv on article;
Query OK, 0 rows affected (0.07 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> create index idx_article_ccv on article (category_id,views);
Query OK, 0 rows affected (0.06 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> show index from  article;
+---------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table   | Non_unique | Key_name        | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+---------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| article |          0 | PRIMARY         |            1 | id          | A         |           5 |     NULL | NULL   |      | BTREE      |         |               |
| article |          1 | idx_article_ccv |            1 | category_id | A         |           3 |     NULL | NULL   |      | BTREE      |         |               |
| article |          1 | idx_article_ccv |            2 | views       | A         |           5 |     NULL | NULL   |      | BTREE      |         |               |
+---------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
3 rows in set (0.00 sec)

  • 再次看一下SQL执行计划
mysql> explain SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
+----+-------------+---------+------------+-------+-----------------+-----------------+---------+------+------+----------+------------------------------------+
| id | select_type | table   | partitions | type  | possible_keys   | key             | key_len | ref  | rows | filtered | Extra                              |
+----+-------------+---------+------------+-------+-----------------+-----------------+---------+------+------+----------+------------------------------------+
|  1 | SIMPLE      | article | NULL       | range | idx_article_ccv | idx_article_ccv | 4       | NULL |    3 |    33.33 | Using index condition; Using where |
+----+-------------+---------+------------+-------+-----------------+-----------------+---------+------+------+----------+------------------------------------+
1 row in set, 1 warning (0.00 sec)

索引两表优化案例

  • 不创建索引的情况下,SQL的执行计划
    • book和class两张表都是没有使用索引,全表扫描,需要进行优化,
mysql> explain select * from book left join class on book.card = class.card;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                              |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
|  1 | SIMPLE      | book  | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | NULL                                               |
|  1 | SIMPLE      | class | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值