当前位置: 首页 > news >正文

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)一起创建,一张表最多只有一个主键索引,索引列的值不允许有空值。

CREATE TABLE table_name (......PRIMARY KEY(index_column_1))USING BTREE
);

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

CREATE TABLE table_name (......UNIQUE KEY(index_column_1,index_column_2,...);
);

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

CREATE UNIQUE INDEX index_name
ON table_name(idx_column_1, index_column_2, ...);

普通索引
普通索引就是建立在普通字段上的索引,既不要求字段为主键,也不要求字段为UNIQUE。
在创建表时,创建普通索引的方式如下:

CREATE TABLE table_name (....INDEX(index_column_1,index_column_2,...);
);

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

CREATE INDEX index_name
ON table_name(index_column_1,index_column_2,...);

前缀索引:前缀索引是指对字符类型字段的前几个字符建立的索引,而不是在整个字符上建立的索引,前缀索引可以建立在字段类型为char、varchar、binary、varbinary的列上。
使用前缀索引的目的是为了减少索引占用的存储空间,提升查询效率。
在创建表时,创建前缀索引的方式如下:

CREATE TABLE table_name(column_list,INDEX(column_name(length))
);

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

CREATE INDEX index_name 
ON table_name(column_name(length));
按字段个数分类

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

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

联合索引:通过将多个字段组合成一个索引,该索引就称为联合索引。
比如,将商品表中的product_no和name字段组合成联合索引(product_no, name),创建联合索引的方式如下:

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,你怎么通过索引来提高查询效率呢?

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约束。
有什么优化索引的方法?
  • 前缀索引优化‘;
  • 覆盖索引优化;
  • 主键索引最好是自增的;
  • 防止索引失效。

从数据页的角度看B+树

为什么 MySQL 采用 B+树作为索引?

MySQL单表不要超过2000W行,靠谱吗?

索引失效有哪些?

count(*)和count(1)有什么区别?哪个性能最好?

MySQL分页有什么性能问题?怎么优化?

http://www.xdnf.cn/news/1208503.html

相关文章:

  • 大模型技术对部分岗位的影响
  • Apache Ignite 的分布式原子类型(Atomic Types)
  • 在CSS中,如果你想设置一个元素的高度(height)与其宽度(width)相匹配,但又希望宽度使用百分比来定义,你可以通过几种方式来实现。
  • 试用SAP BTP 02C:试用SAP HANA Schemas HDI Containers
  • VSCode使用Code Runner运行C/C++输出[Done] exited with code=0 in xxx seconds
  • SpringBoot整合RocketMQ(rocketmq-client.jar)
  • C++ AI流处理核心算法实战
  • MOGA(多目标遗传算法)求解 ZDT1 双目标优化问题
  • 沪铝本周想法
  • 智能编队重构职场生态:Agentic AI 协同时代来临
  • 基于Blazor进销存管理系统
  • 对College数据进行多模型预测(R语言)
  • thingsboard 自定义动作JS编程
  • 【高阶版】R语言空间分析、模拟预测与可视化高级应用
  • 【C++算法】82.BFS解决FloodFill算法_被围绕的区域
  • Java抽Oracle数据时编码问题
  • SpringBoot整合RocketMQ(阿里云ONS)
  • CentOS安装ffmpeg并转码视频为mp4
  • 【腾讯云】EdgeOne免费版实现网站加速与安全防护
  • 通缩漩涡中的测量突围:新启航如何以国产 3D 白光干涉仪劈开半导体成本困局?
  • 橡胶制品加工:塑造生活的柔韧力量
  • SketchUp纹理贴图插件Architextures安装使用图文教程
  • 【Linux】环境变量
  • 字符串函数安全解析成执行函数
  • 【Spring Boot 快速入门】三、分层解耦
  • 论文阅读--射频电源在半导体领域的应用
  • 【nerf处理视频数据】Instant-NGP项目NeRF模型训练数据集准备指南
  • 机器学习线性回归:从基础到实践的入门指南
  • Golang语言如何高效使用字符串
  • VLA--Gemini Robotics On-Device: 将AI带到本地机器人设备上