MySQL 高级(2): 索引的概念,分类,性能分析,索引失效

索引优化分析

索引的概念

  • MySQL 官方对索引的定义为:索引(Index)是帮助MySQL 高效获取数据的数据结构。 可以得到索引的本质:索引是数据结构。可以简单理解为排好序的快速查找数据结构。
  • 在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。 下图就是一种可能的索引方式示例:
    在这里插入图片描述
    左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址。为了加快 Col2 的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指 针
    • 这样就可以运用二叉查找在一定的复杂度内获取到相应数据,从而快速的检索出符合条件的记录。
    • 一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。

优缺点
优势:

  • 提高数据检索的效率,降低数据库的IO成本。
  • 通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗。

劣势:

  • 虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息。
  • 实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的。

索引的结构

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

Btree 查找树

概述

  • Btree又名 B- 树,是一种平衡的多路查找树,它可以利用多个分支节点(子树节点)来减少查询数据时所经历的节点数,从而达到节省存取时间的目的。
    下图是一个M=4 阶的B树:
    在这里插入图片描述

主要特点

  • 每个节点的key数量小于m个(与m-way相同)
  • 除根节点和叶子节点的其他节点存储key的个数必须大于等于m/2
  • 所有叶子节点都处于同一层,也就是说所有叶节点具有相同的深度h(树的高度,也意味着树是平衡的)

Btree的查找

  • 必须从根节点开始采用二分法查找,所以时间复杂度为O(logn)。

Btree的插入

  • 为了保证树的平衡,如果带插入节点的key数量小于m-1个,则直接插入(不会违反第一条特性),否则,需要将该节点分为两部分,再执行该操作。
    ·在这里插入图片描述
  • 下面是往B树中依次插入6 10 4 14 5 11 15 3 2 12 1 7 8 8 6 3 6 21 5 15 15 6 32 23 45 65 7 8 6 5 4 的演示动画:
    在这里插入图片描述

B+Tree 索引

概述

  • 其实B+树与B-树相差并不大,由于 B- 树可能会从最高级的根节点查询到最低级的叶子节点。那我们可以从叶子节点开始查啊,这就产生了B+树结构。
  • 在每一个叶子节点做一个标记,把这些标记存起来,每次查的时候可以在查询根节点的时候从叶子节点也开始查。
  • 示意图如下:
    在这里插入图片描述
    也可以用下面形式表示:
    在这里插入图片描述

下图是B+树的建立过程:

  • 依次插入6 10 4 14 5 11 15 3 2 12 1 7 8 8 6 3 6 21 5 15 15 6 32 23 45 65 7 8 6 5 4 的演示动画:
    在这里插入图片描述

B+ 树与 B- 树的区别

  • B- 树的关键字和记录是放在一起的,叶子节点可以看作外部节点,不包含任何信息;B+ 树的非叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。
  • 在B-树中,越靠近根节点的记录查找时间越快,只要找到关键字即可确定记录的存在;而B+树中每个记录的查找时间基本是一样的,都需要从根节点走到叶子节点,而且在叶子节点中还要再比较关键字

B+ 树的优势

  • B+树的磁盘读写代价更低:而在实际应用中却是B+树的性能要好些。因为B+树的非叶子节点不存放实际的数据,这样每个节点可容纳的元素个数比B-树多,树高比B-树小,这样带来的好处是减少磁盘访问次数。 尽管B+树找到一个记录所需的比较次数要比B-树多,但是一次磁盘访问的时间相当于成百上千次内存比较的时间,因此实际中B+树的性能可能还会好些。
  • B+树的查询效率更加稳定: B+树的叶子节点使用指针连接在一起,方便顺序遍历(例如查看一个目录下的所有文件,一个表中的所有记录等),这也是很多数据库和文件系统使用B+树的缘故。

聚簇索引和非聚簇索引

聚簇索引并不是一种单独的索引类型,而是一种数据存储方式。术语 ‘聚簇’表示数据行和相邻的键值聚簇的存储在一起

  • 如下图,左侧的索引就是聚簇索引,因为数据行在磁盘的排列和索引排序保持一致。
    在这里插入图片描述

聚簇索引的好处:

  • 按照聚簇索引排列顺序,查询显示一定范围数据的时候,由于数据都是紧密相连,数据库不不用从多个数据块中提取数据,所以节省了大量的 io 操作。

聚簇索引的限制:

  • 对于 mysql 数据库目前只有 innodb 数据引擎支持聚簇索引,而 Myisam 并不支持聚簇索引。
  • 由于数据物理存储排序方式只能有一种,所以每个 Mysql 的表只能有一个聚簇索引。一般情况下就是该表的主键。
  • 为了充分利用聚簇索引的聚簇的特性,所以 innodb 表的主键列尽量选用有序的顺序 id,而不建议用无序的 id,比如 uuid 这种。

时间复杂度(扩展)

同一问题可用不同算法解决,而一个算法的质量优劣将影响到算法乃至程序的效率。算法分析的目的在于选择合适算法和改进算法。

  • 时间复杂度是指执行算法所需要的计算工作量,用大 O 表示记为:O(…)
    在这里插入图片描述

索引的分类

单值索引

概念: 即一个索引只包含单个列,一个表可以有多个单列索引。
语法:

  • 和表一起创建:

    CREATE TABLE customer (id INT(10) UNSIGNED AUTO_INCREMENT ,customer_no VARCHAR(200),customer_name
    VARCHAR(200),
    PRIMARY KEY(id),
    KEY (customer_name)
    );
    
  • 单独建表索引:

    CREATE INDEX idx_customer_name ON customer(customer_name);
    

唯一索引

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

  • 和表一起创建

    CREATE TABLE customer (id INT(10) UNSIGNED AUTO_INCREMENT ,customer_no VARCHAR(200),customer_name
    VARCHAR(200),
    PRIMARY KEY(id),
    KEY (customer_name),
    UNIQUE (customer_no)
    );
    
  • 单独建唯一索引:

    CREATE UNIQUE INDEX idx_customer_no ON customer(customer_no);
    

主键索引

概念: 设定为主键后数据库会自动建立索引,innodb 为聚簇索引。

  • 随表创建

    CREATE TABLE customer (id INT(10) UNSIGNED AUTO_INCREMENT ,customer_no VARCHAR(200),customer_name
    VARCHAR(200),
    PRIMARY KEY(id)
    );
    
  • 单独创建

    ALTER TABLE customer add PRIMARY KEY customer(customer_no);
    
  • 删除建主键索引:

    ALTER TABLE customer drop PRIMARY KEY ;
    
  • 修改建主键索引:

    必须先删除掉(drop)原索引,再新建(add)索引
    

复合索引

概念: 即一个索引包含多个列

  • 随表一起建立

    CREATE TABLE customer (id INT(10) UNSIGNED AUTO_INCREMENT ,customer_no VARCHAR(200),customer_name
    VARCHAR(200),
    PRIMARY KEY(id),
    KEY (customer_name),
    UNIQUE (customer_name),
    KEY (customer_no,customer_name)
    );
    
  • 单独建索引:

    CREATE INDEX idx_no_name ON customer(customer_no,customer_name);
    

基本语法

在这里插入图片描述

覆盖索引

select的数据列从索引中就能获取,不必读取数据行,MySQL可以利用索引返回select列表中的字段,不惜根据索引再次读取数据文件,简而言之,就是查询列要被所建的索引覆盖。
在这里插入图片描述

索引的创建时机

适合创建的情况

  • 主键自动建立唯一索引;
  • 频繁作为查询条件的字段应该创建索引
  • 查询中与其它表关联的字段,外键关系建立索引
  • 单键/组合索引的选择问题, 组合索引性价比更高
  • 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
  • 查询中统计或者分组字段

不适合创建的情况

  • 表记录太少
  • 经常增删改的表或者字段
  • Where 条件里用不到的字段不创建索引
  • 过滤性不好的不适合建索引

Explain 性能分析

概念

使用 EXPLAIN 关键字 可以模拟优化器执行SQL 查询语句,从而知道MySQL 是如何处理你的SQL 语句的。分析你的查询语句或是表结构的性能瓶颈。
用法: Explain+SQL 语句。
Explain 执行后返回的信息:
在这里插入图片描述

实例

首先创建 t1,t2,t3,t4 等 四张表,且插入一些数据:

```sql
CREATE TABLE t1(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
CREATE TABLE t2(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
CREATE TABLE t3(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
CREATE TABLE t4(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
INSERT INTO t1(content) VALUES(CONCAT('t1_',FLOOR(1+RAND()*1000)));
INSERT INTO t2(content) VALUES(CONCAT('t2_',FLOOR(1+RAND()*1000)));
INSERT INTO t3(content) VALUES(CONCAT('t3_',FLOOR(1+RAND()*1000)));
INSERT INTO t4(content) VALUES(CONCAT('t4_',FLOOR(1+RAND()*1000)));
```

1、id

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

  • id 号每个号码,表示一趟独立的查询。一个sql 的查询趟数越少越好。
  • ①id 相同,执行顺序由上至下
    在这里插入图片描述
  • ②id 不同,id 不同,如果是子查询,id 的序号会递增,id 值越大优先级越高,越先被执行
    在这里插入图片描述
  • ③有相同也有不同
    在这里插入图片描述
    id 如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id 值越大,优先级越高,越先执行衍生= DERIVED

2、select_type

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

3、table

  • 这个数据是基于哪张表的。

4、type

  • type 是查询的访问类型。是较为重要的一个指标,结果值从最好到最坏依次是:
    system > const > eq_ref > ref > fulltext > ref_or_null > index_merge> unique_subquery > index_subquery > range > index >ALL,一般来说,得保证查询至少达到 range 级别,最好能达到ref。

在这里插入图片描述
具体描述如下:

类型 描述
system 表只有一行记录(等于系统表),这是 const 类型的特列,平时不会出现,这个也可以忽略不计
const 表示通过索引一次就找到了,const 用于比较 primary key 或者 unique 索引。因为只匹配一行数据,所以很快。如将主键置于 where 列表中,MySQL 就能将该查询转换为一个常量。
eq_ref 唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描。
ref 非唯一性索引扫描,返回匹配某个单独值的所有行.本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体。
range 只检索给定范围的行,使用一个索引来选择行。key 列显示使用了哪个索引一般就是在你的 where 语句中出现了 between、<、>、in 等的查询这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束语另一点,不用扫描全部索引
index 出现index是sql使用了索引但是没用通过索引进行过滤,一般是使用了覆盖索引或者是利用索引进行了排序分组。
all Full Table Scan,将遍历全表以找到匹配的行。
index_merge 在查询过程中需要多个索引组合使用,通常出现在有 or 的关键字的 sql 中。
ref_or_null 对于某个字段既需要关联条件,也需要 null 值得情况下。查询优化器会选择用 ref_or_null 连接查询。
index_subquery 利用索引来关联子查询,不再全表扫描。
unique_subquery 该联接类型类似于 index_subquery。 子查询中的唯一索引

5、possible_keys

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

6、key

  • 实际使用的索引。如果为NULL,则没有使用索引。

7、key_len

  • 表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。key_len 字段能够帮你检查是否充分的利用上了索引。ken_len 越长,说明索引使用的越充分
    在这里插入图片描述
    在这里插入图片描述
    计算方法如下:
    • ①先看索引上字段的类型+长度比如int=4 ; varchar(20) =20 ; char(20) =20
    • ②如果是varchar 或者char 这种字符串字段,视字符集要乘不同的值,比如utf-8 要乘3,GBK 要乘2,
    • ③varchar 这种动态字符串要加2 个字节
    • ④允许为空的字段要加1 个字节
      第一组:key_len=age 的字节长度+name 的字节长度=4+1 + ( 20*3+2)=5+62=67
      第二组:key_len=age 的字节长度=4+1=5
    • 通过上面的结果可以得到结论 xxx% 走索引;但是 %xxx% 不走索引

8、ref:

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

9、rows

  • rows 列显示 MySQL 认为它执行查询时必须检查的行数。越少越好!
    在这里插入图片描述

10、Extra

其他的额外重要的信息。

信息类型 描述
Using filesort 说明 mysql 会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL 中无法利用索引完成的排序操作称为“文件排序”。
Using temporary 使了用临时表保存中间结果,MySQL 在对查询结果排序时使用临时表。常见于排序 order by 和分组查询 group by。
Using index 代表表示相应的 select 操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错!如果同时出现 using where,表明索引被用来执行索引键值的查找;如果没有同时出现 using where,表明索引只是用来读取数据而非利用索引执行查找
Using where 表明使用了 where 过滤。
Using join buffer 使用了连接缓存。
impossible where where 子句的值总是 false,不能用来获取任何元组。(即后面的条件没有数据满足)
select tables optimized away 在没有 GROUPBY 子句的情况下,基于索引优化 MIN/MAX 操作或者对于 MyISAM 存储引擎优化 COUNT(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。

举例

下面语句的执行顺序是什么?
在这里插入图片描述
第一行

索引失效

  • 【优化总结口诀】
    全值匹配我最爱,最左前缀要遵守;
    带头大哥不能死,中间兄弟不能断;
    索引列上少计算,范围之后全失效;
    Like百分写最右,覆盖索引不写星;
    不等空值还有or,索引失效要少用;
    VAR引号不可丢,SQL高级也不难!

  • 数据表 staffs 结构 如下:
    在这里插入图片描述

  • 数据表中内容如下
    在这里插入图片描述

  • 数据表中的索引如下:CREATE INDEX idx_staffs_nameAgePos ON emp(name,age,pos);

全值匹配我最爱

  • 如果存在下面这些查询语句,并且使用 explain 获得的结果如下:
    在这里插入图片描述

  • 可以看到当查询语句的条件写的更加详细,尽可能达到全值匹配时,索引的效率最高,key_len 最大

最左前缀要遵守(带头不能死,中间不能断)

概念

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

错误的示例:

  • 带头索引死了: 根据索引的定义,带头的是 “name” 列,如果在查询时没有使用那么列,结果如下:
    在这里插入图片描述可以看到此时所有时效,Type为ALL

  • 中间索引断了: 带头索引生效,其他索引失效:
    在这里插入图片描述

  • 总结: 查询字段与索引字段顺序的不同会导致,索引无法充分使用,甚至索引失效。按照从左到右的顺序,生效位置就是匹配到的索引。

索引列上少计算

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

  • 下图为做了一个 left 函数之后,查询语句变为了 Type 索引无效
    在这里插入图片描述
    sql的left()函数表示的是从字符表达式最左边一个字符开始返回指定数目的字符

  • 处理计算索引无效,转换所有函数也会使索引查询无效。一般我们会加单引号,如果不加相当于做了一次强行装换。如下图所示:
    在这里插入图片描述

Like百分写最右

  • 索引字段使用like以通配符开头(‘%字符串’)时,会导致索引失效而转向全表扫描。但是如果把通配符写在最右边既可以进行索引查询
    在这里插入图片描述

覆盖索引不写星;

问题:解决like ‘%字符串%’时,索引失效问题的方法?
使用覆盖索引可以解决。
在这里插入图片描述
在这里插入图片描述

范围之后全失效

  • mysql 存储引擎不能继续使用索引中范围条件(bettween、<、>、in等 )右边的列。注意只有范围之后的索引失效
    在这里插入图片描述
  • 原理很简单,在复合索引创建了一个b+树。每个节点可能有多种个列数据。在创建B+树是,会有个排序的操作。排序的方法是 索引中的第一列先排序,然后是第二列,然后是第三列,然后是第三列…等。如下图示所示:
    在这里插入图片描述
    如果找的是 id>10001的。索引值直接查找到 10001这个节点后,把该节点后面的所有叶子节点全部返回。换句话说,也就是找到一个叶子节点后,由于每个每个叶子节点之间是存在一个指针的,所以无需寻找其他的索引内容。会直接把该叶子节点的左边或者后边全部返回。

不等空值还有or,索引失效要少用;

  • 索引字段上使用(!= 或者 < >)判断时,会导致索引失效而转向全表扫描
    在这里插入图片描述

  • 索引字段上使用 is null / is not null 判断时,会导致索引失效而转向全表扫描
    在这里插入图片描述

  • 索引字段使用 or 时,会导致索引失效而转向全表扫描
    在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Mr_tianyanxiaobai/article/details/119873655