MySQL【索引】篇:索引的分类、B+树、创建索引的原则、索引失效的情况...

思维导图

一、什么是索引?

什么是索引?

索引是提升查询速度的一种数据结构。

当你想查阅书中某个知识的内容,你会选择一页一页的找呢?还是在书的目录去找呢?

傻瓜都知道时间是宝贵的,当然是选择在书的目录去找,找到后再翻到对应的页。

书中的目录 ,就是充当索引的角色,方便我们快速查找书中的内容。

数据库中除了维护数据以外,还会额外维护索引,所以索引是++以空间换时间++的设计思想。

综上可得:

索引就是++帮助存储引擎快速获取数据的一种数据结构++ ,形象的说++索引就是数据的目录++。

有了索引之后,MySQL 不需要一行一行地全表扫描,而是能像查字典一样,沿着 B+ 树快速定位到数据,++大幅减少 IO 和 CPU 的消耗++。

为什么索引能减少 IO 消耗?

补充知识:IO 是什么?

IO,全称磁盘 IO,其中 I 表示 input,就是写数据,O 表示 output,就是读数据。

所以 IO 就是++数据库读取磁盘数据的次数++。

数据库的数据都存储在磁盘上,

  • 每次读取数据就是从磁盘读取数据到内存上,
  • 每次写入数据就是把内存的数据写到磁盘里。

因为磁盘很慢,内存很快。 一次磁盘 IO 可能是 几毫秒 ,但一次内存访问是 纳秒级

所以数据库最贵的成本就是磁盘 IO。

因为索引是 B+ 树结构。

  • 当某个字段无索引时,查找数据得全表扫描,查找速度为 O(n),非常慢;
  • 当某个字段有索引时,查找数据在 B+ 树里二分查找,查找速度为 O(log n),非常快。

例如:我们要查找 user 表中所有 age = 30 的记录。

sql 复制代码
SELECT * FROM user WHERE age = 30;

无索引时:全表扫描

如果 age 没索引:

👉 必须从第一行扫描到最后一行

👉 每一行都要读磁盘,这就产生了大量 IO

时间复杂度 = O(n),非常慢。

有索引时:B+ 树查找

如果 age 有索引:

👉 直接在 B+ 树里二分查找

👉 一次定位到磁盘页

👉 最多只需要 2~3 次磁盘 IO(树高通常 2~4 层)

时间复杂度 = O(log n) ,极快。

为什么索引能减少 CPU 消耗?

**补充知识:CPU 消耗是什么? **

CPU 消耗指的是:CPU 用来进行计算、比较、排序、扫描数据的时间。

比如:

  • 扫描一行行数据进行比较
  • 做排序(ORDER BY)
  • 做 MIN(), MAX() 运算
  • 扫描 where 条件

这些都属于 CPU 消耗。

因为索引是 B+ 树结构。

  • 当某个字段无索引 时,查找数据得全表扫描
    • 从第一行开始,一行一行地++比对字段值++,直到找到目标行。
    • 每一行都是一次 CPU 运算。 ** **
  • 当某个字段有索引 时,查找数据则是通过 B+ 树进行二分查找。
    • 只需要每层做一次 key 比较
    • 例如三层 B+ 树,只需 3~5 次比较就能找到数据。 ** **

例如:我们要查找 user 表中++同时满足 age = 30 且 status = 1 的记录++。

sql 复制代码
SELECT * FROM user WHERE age = 30 AND status = 1

无索引时:每一行都必须做条件判断

全表扫描 = 每行都要做以下判断:

复制代码
- CPU 比较:age == 30?
- CPU 比较:status == 1?

👉 数据越大,CPU越累。

有索引时:B+ 树已经帮你排好序 + 过滤好范围

比如 age=30 放在 B+ 树的同一段叶子节点上。

CPU 不需要一行行判断,只需要扫描叶子节点的一小段。

👉 显著减少 CPU 工作量


++面试口语版:什么是索引?++

索引就是++帮助存储引擎快速获取数据的一种数据结构++ 形象的说++索引就是数据库的++ ++目录++****。

MySQL 除了维护数据以外,还会额外维护索引的数据结构,这属于++以空间换时间++。

有了索引之后,MySQL 就不需要一行一行地全表扫描,而是能像查字典一样,沿着 B+ 树快速定位到数据,大幅减少 IO 和 CPU 的消耗。

二、索引的分类

按数据结构分类

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

++每一种存储引擎支持的索引类型不一定相同++,下表中总结了 MySQL 常见的存储引擎 InnoDB、MyISAM 和 Memory 分别支持的索引类型。

按物理存储分类

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

聚簇索引

  • 聚簇索引的 B+Tree 的叶子节点存放的是实际数据,所有完整的用户记录都存放在主键索引的 B+Tree 的叶子节点里;

二级索引

  • 二级索引的 B+Tree 的叶子节点存放的是主键值,而不是实际数据。

后面我们会详细介绍这两个索引。

按字段特性分类

从字段特性的角度来看,索引分为主键索引、唯一索引、普通索引和前缀索引。

主键索引

主键索引(PRIMARY KEY) 就是++建立在主键字段上的索引++,通常在创建表的时候一起创建。

  • 每张表必须有一个主键索引(可以是你建的,也可以是 Innodb 自己生成的隐藏主键)。
  • 主键索引列的值 不可以为 NULL
  • 主键索引就是聚簇索引
    数据行按主键顺序存储在 B+Tree 的叶子节点。

建表时创建

在创建表时,创建主键索引的方式如下:

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

示例:创建 user 表的时候,给 id 创建主键索引。

写法1:

sql 复制代码
CREATE TABLE user (
    id          BIGINT      NOT NULL,
    username    VARCHAR(50) NOT NULL,
    email       VARCHAR(100),
    phone       VARCHAR(20),
    created_at  DATETIME,
    PRIMARY KEY (id)
) ENGINE=InnoDB;

写法2:

sql 复制代码
CREATE TABLE user (
    id BIGINT NOT NULL PRIMARY KEY,
    ...
);

建表后创建

sql 复制代码
ALTER TABLE table_name
ADD PRIMARY KEY (column_name);

示例:给 user 表的 id 字段设置为主键

sql 复制代码
ALTER TABLE user
ADD PRIMARY KEY (id);

唯一索引

唯一索引(UNIQUE)就是建立在 UNIQUE 字段上的索引。

  • 一张表可以创建多个唯一索引。
  • ++索引列的值不能重复++,但可以为 NULL。
  • 唯一索引用来保证数据不会重复。
  • 常见的唯一索引有:手机号、身份证号等。

建表时创建

方式 1: 在建表时,使用 CREATE TABLE 创建唯一索引

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

示例:在创建 user 表时,给 username 和 email 两个字段创建唯一索引。

sql 复制代码
CREATE TABLE user (
    id BIGINT PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100),
    UNIQUE KEY uk_username (username),      -- 唯一索引
    UNIQUE KEY uk_email (email)            -- 再建一个
);

方式2: 在建表时,使用 CREATE UNIQUE INDEX 创建唯一索引

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

示例:

sql 复制代码
CREATE UNIQUE INDEX uk_username ON user(username);

建表后创建

方式 3:创建表后,使用 ALTER TABLE 添加唯一索引

sql 复制代码
ALTER TABLE table_name
ADD UNIQUE KEY idex_name (column_name);

示例:在创建 user 表后,给 username 字段创建唯一索引 uk_username。

sql 复制代码
ALTER TABLE user ADD UNIQUE KEY uk_username (username);

普通索引

普通索引就是建立在普通字段上的索引,是最基础、最常用的索引。它既不要求字段为主键,也不要求字段为 UNIQUE,只是用来提高查询效率。

建表时创建

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

sql 复制代码
CREATE TABLE table_name (
  ...
  INDEX idx_name (index_column)
);

示例:

sql 复制代码
CREATE TABLE user (
    id BIGINT PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100),
    phone VARCHAR(20),
    INDEX idx_phone (phone)      -- 普通索引
);

建表后创建

建表后,如果要创建普通索引,可以使用下面这条命令:

方式1:

sql 复制代码
CREATE INDEX idx_name ON table_name(index_column);

示例:

sql 复制代码
CREATE INDEX idx_phone ON user(phone);

方式2:

sql 复制代码
ALTER TABLE table_name ADD INDEX idx_name (idx_column);

示例:

sql 复制代码
ALTER TABLE user ADD INDEX idx_phone (phone);

前缀索引

前缀索引(PREFIX INDEX)就是++对字符串类型字段的++ ++前几个字符++ ++建立的索引++,而不是对整个字段建立索引。

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

经典用途有:长字符串,如文章标题、URL、邮箱。

注意:前缀索引不能用于 ORDER BY、GROUP BY 完全覆盖查询。

建表时创建

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

sql 复制代码
CREATE TABLE table_name (
    ...,
    column_name column_type,
    INDEX idx_name (column_name(length))
);

示例:

sql 复制代码
CREATE TABLE user (
    ...,
    email VARCHAR(100),
    INDEX idx_email_prefix (email(10))
);

建表后创建

sql 复制代码
CREATE INDEX idx_name ON tab_name(col_name(length));

示例: 假设 email 很长,我们只索引前 10 个字符。

sql 复制代码
CREATE INDEX idx_email_prefix ON user(email(10));

按字段个数分类

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

单列索引

单列索引,就是++建立在单列字段上的索引++,比如主键索引。

联合索引

联合索引(COMPOSITE INDEX) ,就是++建立在多个字段上的索引++。

  • 会遵守 最左前缀原则:从最左边字段开始,连续使用多少,就能利用多少索引。
  • 应用于多条件查询(where a=x and b=y)。

一般在建表后,创建联合索引,命令如下:

sql 复制代码
CREATE INDEX idx_a_b_c ON table(a, b, c);

示例:将用户表中的 name 和 age 字段组合成联合索引(name, age),创建的联合索引如下:

sql 复制代码
CREATE INDEX idx_name_age ON table(name, age);

MySQL 索引总结表

索引类型 是否唯一 说明
主键索引 聚簇索引,数据按主键排序
唯一索引 允许多个 NULL
普通索引 最常用
前缀索引 长字符串节省空间
联合索引 取决于是否 UNIQUE 多字段索引,遵循最左匹配

++面试口语版:MySQL 有哪些索引?++

MySQL 主要有++主键索引、普通索引、唯一索引、前缀索引和联合索引++这几种索引。

  • Innodb 引擎会要求++每一张表都必须要有一个++ ++主键索引++,比如表里的 id 字段就是主键索引。
  • 对于需要++保证数据不重复++ 的字段,如手机号、身份证号等,我们可以建立**++唯一索引++**。
  • 针对++查询比较频繁的字段++ ,我们可以针对这个字段建立最常用的**++普通索引++**。
  • 对于长字符字段,比如文章标题、商品名称等,我们可以++只对这些字段的前缀部分建立索引++ ,这就是**++前缀索引++**,可以减少索引的存储空间。
  • 如果是++多个字段++ 的话,可以考虑建立**++联合索引++ **,利用++多字段组合查询++来提高查询效率。

三、B+树

B+ 树由根节点(root node)、中间节点(non leaf node)、叶子节点(leaf node)组成。

其中叶子节点存放所有排序后的数据。

当然存在一种特殊情况,就是高度为 1 的 B+ 树索引:

因为该 B+ 树的高度为 1,只有一个页,所以该页既是根节点也是叶子节点。

四、最左前缀匹配原则是什么?

最左匹配原则说的是:联合索引一定要从最左边的列开始使用。

比如我建了一个 (a,b,c) 的联合索引,它在底层是先按 a 排序,a 相同再按 b,b 相同再按 c。所以查询条件必须按这个顺序来走,

只要从最左边开始连续使用,就可以命中索引。

如果跳过了 a,比如只查 b、c,那索引就废了。

另外,如果某一列用了范围查询(例如 >、<、between),索引会在这一列后停止继续匹配。

通俗来说:联合索引是排好序的队伍,你必须从队伍最左边开始检查,否则用不上。

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

索引最大的好处是提高查询速度,但是索引也是有缺点的,比如:

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

所以,索引不是万能钥匙,它也是根据场景来使用的。

六、创建索引时需要注意什么?

  • 面试官:那你是怎么控制索引的数量的,一般控制在多少?

七、索引失效的情况有哪些?

  1. 索引违反最左前缀法则

where 跳过了 name,违反最左前缀法则,导致索引失效:

如果符合最左前缀法则,但是跳跃了其中某一列,则只有最左索引 name 会生效,导致 address 索引失效:

  1. 范围查询右边的列,不能使用索引:

对 status 进行范围查询,所以 status 右边的列 address 不能使用索引。

前两个字段 name 和 status 是走索引的,但是最后一个字段 address 没有走索引。

  1. 不要在索引列上进行运算操作,否则索引会失效

数据会正常返回,但是通过 explain 分析发现索引失效。

  1. 字符串不加单引号**,有可能会造成索引失效**
  • 类型转换会造成索引失效
  1. 模糊查询****有可能会导致索引失效

以 % 开头的 Like 模糊查询,索引失效。

如果仅仅是尾部模糊匹配,索引不会失效。

如果是头部模糊匹配,索引失效

其他的索引失效情况:

  • or 左边的条件索引失效也会导致右边的索引失效
  • 不等于和 is null 也会索引失效
  • 索引字段数据有null的记录也会失效
相关推荐
z***89711 小时前
[golang][MAC]Go环境搭建+VsCode配置
vscode·macos·golang
想不明白的过度思考者1 小时前
数据库基础与MySQL核心组件解析
数据库·mysql·oracle
小年糕是糕手1 小时前
【C++】类和对象(三) -- 拷贝构造函数、赋值运算符重载
开发语言·c++·程序人生·考研·github·个人开发·改行学it
f***24111 小时前
Spring Boot接收参数的19种方式
java·spring boot·后端
xunyan62341 小时前
面向对象(下)-设计模式与单例设计模式
java·单例模式·设计模式
杰克尼1 小时前
蓝桥云课-小蓝做题
java·数据结构·算法
艾莉丝努力练剑1 小时前
【C++:C++11收尾】解构C++可调用对象:从入门到精通,掌握function包装器与bind适配器包装器详解
java·开发语言·c++·人工智能·c++11·右值引用
CNRio1 小时前
第七章-DockerSwarm:容器集群的‘指挥官‘
java·开发语言·容器