MySQL图解索引篇

二、索引篇

索引 什么是索引 索引的分类 按数据结构分类 B+tree索引 Hash索引 Full-text索引 按物理存储分类 聚簇索引 二次索引 按字段特性分类 主键索引 唯一索引 前缀索引 按字段个数分类 单列索引 联合索引 什么时候需要/不需要创建索引 什么时候适用索引 什么时候不需要创建索引 有什么优化索引的方法 前缀索引优化 覆盖索引优化 主键索引最好是自增的 防止索引失效

索引常见面试题

什么是索引?

索引是数据的目录。

下图是 MySQL 的结构图,索引和数据就是位于存储引擎中:

按数据结构分类

从数据结构的角度来看,MySQL常见索引有B+ Tree索引,HASH索引,Full-Text索引。

InnoDB是在MySQL 5.5之后成为默认的MySQL存储引擎。B+Tree索引类型也是MySQL存储引擎采用最多的索引类型。

在创建表时,InnoDB存储引擎会根据不同的场景选择不同的列作为索引:

  • 如果有主键,默认会使用主键作为聚簇索引的索引键(key);
  • 如果没有主键,就选择第一个不包含NULL值的唯一列作为聚簇索引的索引键(key);
  • 在上面两个都没有的情况下,InnoDB将自动生成一个隐式自增iD列作为聚簇索引的索引键(key)。

其他索引都属于辅助索引,也被称为二级索引或非聚簇索引。创建的主键索引和二级索引默认使用的是B+ Tree索引。

B+Tree存储千万级的数据只需要3-4层高度就可以满足,这意味着从千万级的表查询目标数据最多需要3-4次磁盘I/O,所以B+Tree相比于B树和二叉树来说,最大的优势在于查询效率很高,因为即使在数据量很大的情况,查询一个数据的磁盘1/O依然维持在3-4次。

在二级索引的B+ Tree就能查询到结果的过程就叫作「覆盖索引」,也就是只需要查一个B+ Tree就能找到数据。

按物理存储分类

从数据存的角度来看,索引分为聚簇索引(主键索引)、二级索引(辅助索引)。

这两个区分:

  • 主键索引的B+ Tree的叶子节点存放的是实际数据,所有完整的用户记录都存放在主键索引的B+ Tree的叶子节点上。
  • 二级索引的B+ Tree的叶子节点存放的是主键值,而不是实际数据。

所以,在查询时使用了二级索引1,如果查询的数据能在二级索引里查询的到,那么就不需要回表,这个过程就是覆盖索引l。如果查询的数据不在二级索引里,就会先检索二级索引,找到对应的叶子节点,获取到主键值后,然后再检索主键索引,就能查询到数据了,这个过程就是回表。

按字段特性分类

从字段特性的角度来看,索引分为主键索引、唯一索引、普通索引、前缀索引。
主键索引:主键索引是建立在主键字段上的索引,通过在创建表的时候(PRIMARY KEY)一起创建,一张表最多只有一个主键索引,索引列的值不允许有空值。

sql 复制代码
CREATE TABLE table_name (
	......
	PRIMARY KEY(index_column_1))USING BTREE
);

唯一索引:唯一索引建立在UNIQUE字段上的索引,一张表可以有多个唯一索引,索引列的值必须唯一,但是允许有空值。

sql 复制代码
CREATE TABLE table_name (
	......
	UNIQUE KEY(index_column_1,index_column_2,...);
);

建表后,如果要创建唯一索引,可以使用这条命令:

sql 复制代码
CREATE UNIQUE INDEX index_name
ON table_name(idx_column_1, index_column_2, ...);

普通索引

普通索引就是建立在普通字段上的索引,既不要求字段为主键,也不要求字段为UNIQUE。

在创建表时,创建普通索引的方式如下:

sql 复制代码
CREATE TABLE table_name (
	....
	INDEX(index_column_1,index_column_2,...);
);

建表后,如果要创建普通索引,可以:

sql 复制代码
CREATE INDEX index_name
ON table_name(index_column_1,index_column_2,...);

前缀索引 :前缀索引是指对字符类型字段的前几个字符建立的索引,而不是在整个字符上建立的索引,前缀索引可以建立在字段类型为char、varchar、binary、varbinary的列上。

使用前缀索引的目的是为了减少索引占用的存储空间,提升查询效率。

在创建表时,创建前缀索引的方式如下:

sql 复制代码
CREATE TABLE table_name(
	column_list,
	INDEX(column_name(length))
);

建表后,如果要创建前缀索引,可以使用命令:

sql 复制代码
CREATE INDEX index_name 
ON table_name(column_name(length));
按字段个数分类

从字段个数的角度来看,索引分为单列索引、联合索引(复合索引).

  • 建立在单列上的索引称为单列索引,比如主键索引;
  • 建立在多列上的索引称为联合索引。

联合索引:通过将多个字段组合成一个索引,该索引就称为联合索引。

比如,将商品表中的product_no和name字段组合成联合索引(product_no, name),创建联合索引的方式如下:

sql 复制代码
CAEATE INDEX index_product_no_name ON product(product_no, name);

联合索引的非叶子节点用两个字段的值作为B+Tree的key值。当在联合索引查询数据时,先按product_no字段比较,在product_no相同的情况下再按name字段比较。

也就是说,使用联合索引是时,存在最左匹配原则,也就是按照最左优先的方式进行索引的匹配。在使用联合索引进行查询的时候,如果不遵循「最左匹配原则」,联合索引就会失效,这样就无法利用到索引快速查询的特性了。

联合索引范围查询

并不是查询过程使用了联合索引查询,就代表联合索引中的所有字段都用到了联合索引进行索引查询,也就是可能存在部分字段用到联合索引的B+Tree,部分字段没有用到联合索引的B+Tree的情况。

这种特殊情况就发生在范围查询,联合索引的最左匹配原则会一直向右匹配知道遇到「范围查询」就会停止匹配。也就是范围查询的字段可以用到联合索引,但是在范围查询字段的后面的字段无法用到联合索引。

联合索引的最左匹配原则,在遇到范围查询(如> 、<)的时候,就会停止匹配,也就是范围查询的字段可以用到联合索引,但是在范围查询字段的后面的字段无法用到联合索引。注意,对于>=、<=、BETWEEN、like前缀匹配的范围查询,并不会停止匹配。

索引下推

现在我们知道,对于联合索引l(a,b),在执行select* from table where a >1 and b = 2语句的时候,只有a字段能用到索引l,那在联合索引l的B+Tree找到第一个满足条件的主键值(ID为2)后,还需要判断其他条件是否满足(看b是否等于2),那是在联合索引里判断?还是回主键索引去判断呢?

  • 在MySQL 5.6之前,只能从ID2(主键值)开始一个个回表,到「主键索引」上找到数据行,再对比b字段值。
  • 而MySQL 5.6引入的索引下推优化,可以在联合索引遍历过程中,对联合索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。
索引区分度

实际开发过程中建立联合索引时,要把区分度大的字段排在前面,这样区分大的字段越有可能被更多的SQL使用到。

区分度就是某个字段column不同值的个数「除以」表的总行数,计算公式如下:

比如,性别的区分度就很小,不适合建立索引或不适合排在联合索引列的靠前的位置,而UUID这类字段就比较适合做索引或排在联合索引列的靠前的位置。

MySQL还有一个查询优化器,查询优化器发现某个值出现在表的数据行中的百分比(惯用的百分比界线是"30%")很高的时候,它一般会忽略索引,进行全表扫描。

联合索引进行排序(???)

这里出一个题目,针对下面这条SQL,你怎么通过索引来提高查询效率呢?

sql 复制代码
select * from order where status = 1 order by create_time asc

但是更好的方式给 status 和 create_time 列建立一个联合索引l,因为这样可以避免 MySQL数据库发生文件排序。

因为在查询时,如果只用到 status 的索引l,但是这条语句还要对create_time 排序,这时就要用文件排序filesort,也就是在 SQL 执行计划中,Extra 列会出现 Using filesort。所以,要利用索引l的有序性,在status和create_time列建立联合索引,这样根据status 筛选后的数据就

是按照create_time 排好序的,避免在文件排序,提高了查询效率。

什么时候需要/不需要创建索引?

索引的好处:提高查询速度;

索引的缺点:

  • 需要占用物理空间,数量越大,占用空间越大;
  • 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增大;
  • 会降低表的增删改的效率,因为每次增删改索引,B+树为了维护索引有序性,都需要进行动态维护。

什么时候适用索引?

  • 字段有唯一性限制的,比如商品编码;
  • 经常用于WHERE查询条件的字段;
  • 经常用于GROUP BY和ORDER BY的字段。

什么时候不需要创建索引?

  • WHERE条件,GROUP BY,ORDER BY里用不到的字段;
  • 字段中存在大量重复数据,不需要创建索引;
  • 表数据太少的时候,不需要创建索引;
  • 经常更新的字段不用创建索引。
有什么优化索引的方式?

常见优化索引的方法:

  • 前缀索引优化:使用某个字段中字符串的前几个字符建立索引。
    使用前缀索引是为了减少索引字段大小,可以增加一个索引页中存储的索引值,有效提高索引的查询速度。在一些大字符串的字作为索引时,使用前缀索引可以帮助我们减少索引项的大小。
    前缀索引的局限性:
    • order by就无法使用前缀索引;
    • 无法把前缀索引用作覆盖索引。
  • 覆盖索引优化:覆盖索引是指SQL中query的所有字段,在索引B+ Tree的叶子节点上都能找得到的那些索引,从二级索引中查询得到记录,而不需要通过聚簇索引查询获得,可以避免回表的操作。使用覆盖索引的好处就是,不需要查询出包含整行记录的所有信息,也就减少了大量的I/O操作。
  • 主键索引最好是自增的:InnoDB创建主键索引默认为聚簇索引l,数据被存放在了B+Tree的叶子节点上。也就是说,同一个叶子节
    点内的各个数据是按主键顺序存放的,因此,每当有一条新的数据插入时,数据库会根据主键将其插入到对应的叶子节点中。
    • 使用自增主键,插入一条新纪录,都是追加操作,不需要重新移动数据。使用非自增主键,会导致页分裂,还可能造成大量的内存碎片,导致索引结构不紧凑,从而影响重新效率。
  • 防止索引失效:用上了索引并不意味着查询的时候会使用到索引l,所以我们心里要清楚有哪些情况会导致索引失效,从而避免写出索引失效的查询语句,否则这样的查询效率是很低的。(常见面试题:说实话你知道哪些情况下会发生索引失效)
索引最好设置为NOT NULL
  • 索引列存在NULL就会导致优化器在做索引选择的时候更加复杂,更加难以优化,因为可为NULL的列会使索引、索引统计和值比较都更复杂,比如进行索引统计时,count会省略为NULL的行。
  • NULL值是一个没意义的值,会占用物理空间,行格式中至少会用1字节空间存储NULL值列表。
总结
为什么MySQL InnoDB选择B+tree作为索引的数据结构?

B+Tree vs B Tree:

  • 存储相同数据量级别的情况下,B+Tree树高比B Tree低,磁盘I/O次数更少。
  • B+Tree叶子节点用双向链表串起来,适合范围查询,B Tree无法做到这点。

B+Tree vs 二叉树:

  • 随着数据量的增加,二叉树的树高会越来越高,磁盘I/O次数也会更多,B+Tree在千万级别的数据下,高度依然维持在3~4次层左右,也就是说一次数据查询操作只需要做3~4次的磁盘I/O操作就能查询到目标数据。

B+ Tree vs Hash:

  • 虽然Hash的等值查询效率很高,但是无法做范围查询
什么时候适用索引?
  • 字段有唯一性限制的,比如商品编码;
  • 经常用于WHERE查询条件的字段;
  • 经常用于GROUP BY和ORDER BY的字段。
什么时候不需要创建索引?
  • WHERE条件,GROUP BY,ORDER BY里用不到的字段;
  • 字段中存在大量重复数据,不需要创建索引;
  • 表数据太少的时候,不需要创建索引;
  • 经常更新的字段不用创建索引。
什么时候索引会失效?
  • 当我们适用左或者左右模糊匹配的时候,也就是like %xx或者like %xx%这两种方式都会造成索引失效;
  • 当我们在查询条件中对索引列做了计算、函数、类型转换操作,会导致索引失败;
  • 联合索引要能正确适用需要遵循最左匹配原则,也就是按照最左优先的方式进行索引的匹配,否则就会导致索引失效;
  • 在WHERE子句中,如果在OR前的条件列是索引列,而在OR后的条件列不是索引列,那么索引就会失效;
  • 为了更好的利用,索引列要设置为NOT NULL约束。
有什么优化索引的方法?
  • 前缀索引优化';
  • 覆盖索引优化;
  • 主键索引最好是自增的;
  • 防止索引失效。
相关推荐
DemonAvenger1 小时前
SQL语句详解:SELECT查询的艺术 —— 从基础到实战的进阶指南
数据库·mysql·性能优化
程序猿小D1 小时前
基于SpringBoot+MyBatis+MySQL+VUE实现的便利店信息管理系统(附源码+数据库+毕业论文+远程部署)
数据库·spring boot·mysql·vue·mybatis·毕业论文·便利店信息管理系统
bytebeats1 小时前
移动开发中特性开关的最佳实践
android·firebase
bytebeats1 小时前
Android 开发者的 Jetpack Compose 学习路线图
android·android jetpack
bytebeats1 小时前
Android 的未来: 为什么 `Navigation 3` 是行业变革者!
android·android jetpack
bytebeats1 小时前
如何在 Jetpack Compose 中创建逐字动画
android·android jetpack
Billy_Zuo2 小时前
Android调用python库和方法的实现
android·开发语言·python
飞翔的佩奇2 小时前
Java项目:基于SSM框架实现的济南旅游网站管理系统【ssm+B/S架构+源码+数据库+毕业论文+远程部署】
java·数据库·mysql·毕业设计·ssm·旅游·毕业论文
rannn_1112 小时前
【MySQL学习|黑马笔记|Day3】多表查询(多表关系、内连接、外连接、自连接、联合查询、子查询),事务(简介、操作、四大体系、并发事务问题、事务隔离级别)
数据库·笔记·后端·学习·mysql
打野二师兄3 小时前
Undo、Redo、Binlog的相爱相杀
mysql·mys