MySQL 存储引擎与索引深度解析:从原理到优化实践

复制代码
感谢阅读!❤️
如果这篇文章对你有帮助,欢迎 **点赞** 👍 和 **关注** ⭐,获取更多实用技巧和干货内容!你的支持是我持续创作的动力!
**关注我,不错过每一篇精彩内容!**

目录

  • 一、存储引擎
    • [1.1 存储引擎分类](#1.1 存储引擎分类)
    • [1.2 查看与修改存储引擎](#1.2 查看与修改存储引擎)
    • [1.3 存储引擎的适用场景](#1.3 存储引擎的适用场景)
  • 二、索引
    • [2.1 什么是索引?](#2.1 什么是索引?)
    • [2.2 索引的分类](#2.2 索引的分类)
    • [2.3 索引的操作](#2.3 索引的操作)
    • [2.4 MySQL 索引采用了 B+树数据结构](#2.4 MySQL 索引采用了 B+树数据结构)
      • [2.4.1 二叉树](#2.4.1 二叉树)
      • [2.4.2 红黑树(自平衡二叉树)](#2.4.2 红黑树(自平衡二叉树))
      • [2.4.3 B树](#2.4.3 B树)
      • [2.4.4 B+树](#2.4.4 B+树)
    • [2.5 索引类型及核心优化机制](#2.5 索引类型及核心优化机制)
      • [2.5.1 Hash索引](#2.5.1 Hash索引)
      • [2.5.2 聚簇索引和非聚簇索引](#2.5.2 聚簇索引和非聚簇索引)
      • [2.5.3 二级索引](#2.5.3 二级索引)
      • [2.5.4 单列索引(单一索引)](#2.5.4 单列索引(单一索引))
      • [2.5.5 多列索引(复合索引)](#2.5.5 多列索引(复合索引))
      • [2.5.6 覆盖索引](#2.5.6 覆盖索引)
      • [2.5.7 索引下推](#2.5.7 索引下推)
    • [2.6 索引优缺点](#2.6 索引优缺点)
    • [2.7 索引的适用场景和不适用场景](#2.7 索引的适用场景和不适用场景)

一、存储引擎

存储引擎(Storage Engine)是数据库管理系统(DBMS)中负责数据的物理存储、检索和管理的核心组件。不同的存储引擎在事务支持、并发控制、索引结构、锁机制、崩溃恢复等方面具有不同特性。不同的存储引擎实现了不同的存储和检索算法,因此在处理和管理数据的方式上存在差异。不同的存储引擎适用于不同的应用场景。

1.1 存储引擎分类

按功能与特性分类(以 MySQL 为例):

分类 存储引擎 特点
事务型 InnoDBNDB 支持 ACID 事务、行级锁、崩溃恢复
非事务型 MyISAMMemoryCSVArchive 不支持事务,性能或用途特殊
内存型 MemoryNDB(部分) 数据存于内存,速度快但易失
归档/压缩型 Archive 高压缩比,仅支持插入和查询
文件交换型 CSV 数据以 CSV 文件格式存储,便于外部读取
虚拟/特殊用途 BlackholeFederated Blackhole 丢弃数据;Federated 访问远程表

注:PostgreSQLOracleSQL Server 等通常不提供用户可选的多引擎,而是采用统一存储模型,但可通过表类型或扩展实现类似功能。

1.2 查看与修改存储引擎

  • 查看所有的存储引擎
sql 复制代码
SHOW ENGINES;
  • 查看当前默认存储引擎
sql 复制代码
SHOW VARIABLES LIKE 'default_storage_engine';
  • 查看某张表使用的存储引擎
sql 复制代码
SHOW CREATE TABLE 表名;

-- 示例:查看字段ENGINE
SHOW CREATE TABLE STUDENT; -- ENGINE=InnoDB
  • 创建表时指定存储引擎
sql 复制代码
CREATE TABLE 表名(
    ...
) ENGINE = 存储引擎;

-- 示例:
DROP TABLE IF EXISTS USER;

CREATE TABLE USER(
    ID INT PRIMARY KEY,
    NAME VARCHAR(255)
) ENGINE = MEMORY;
  • 修改已有表的存储引擎
sql 复制代码
ALTER TABLE 表名 ENGINE = 新的存储引擎;

-- 修改为 将 MyISAM 存储引擎
ALTER TABLE USER ENGINE = MyISAM;

注意,在修改存储引擎之前,需要考虑以下几点:

  • 修改存储引擎可能需要执行复制表的操作,因此可能会造成数据的丢失或不可用。确保在执行修改之前备份好数据。
  • 修改表的存储引擎可能会影响到现在的应用程序和查询。确保在修改之前评估和测试所有影响。
  • 不是所有的存储引擎都支持相同的功能,确保选择的新存储引擎支持应用程序的所需的功能。
  • ALTER TABLE语句可能需要适当的权限才能执行。确保操作的用户有足够权限来执行修改存储引擎的操作。

1.3 存储引擎的适用场景

1. InnoDB

  • 特点:
    • 提供完整的ACID事务支持
    • 实现行级锁和MVCC
    • 支持外键约束
    • 具有较好的并发性能和数据完整性
  • 适用场景:
    • 高并发系统
    • 需要数据一致性和完整性的业务
    • 频繁读写混合操作
    • 适用于大多数应用场景

2. MyISAMMySQL 8.0+ 已逐步弃用,新项目不推荐使用)

  • 特点:

    • 快速读取,表结构简单
    • 支持全文索引(早期版本优势)
    • 使用 表级锁,写操作会阻塞整个表
    • 不支持事务、外键、崩溃恢复
  • 适用场景:

    • 只读或读多于写的场景(如日志分析、报表)
    • 对事务无要求的小型应用

3. Memory (原名HEAP

  • 特点:
    • 所有数据存储在内存中,访问极快
    • 服务重启后数据丢失
    • 使用哈希索引(默认)或B-Tree
    • 不支持 TEXT/BLOB 类型,不支持事务
  • 适用场景:
    • 临时缓存表(如会话状态、中间计算结果)
    • 高速查找的维度表(需容忍数据丢失)
    • 适用于需要快速读写的临时数据集、缓存和临时表等场景

4. Archive

  • 特点:
    • 专为 高压缩归档 设计(使用 zlib 压缩)
    • 仅支持 INSERTSELECT
    • 不支持删除、更新、索引(MySQL 5.1+ 支持单列索引)
    • 写入快,存储空间小
    • 将数据高效地进行压缩和存储的存储引擎
  • 适用场景:
    • 日志归档、历史数据冷存储
    • 审计记录、传感器历史等只追加场景
    • 适用于需要长期存储大量历史数据且不经常查询的场景

5. CSV

  • 特点:
    • 表数据以标准 CSV 文件 形式存储(.csv 文件)
    • 可被 Excel、Python、文本编辑器直接打开
    • 不支持索引、NULL、事务
  • 适用场景
    • 数据导入/导出中间格式
    • 与其他系统进行简单数据交换

6. Blackhole

  • 特点:
    • 所有写入操作被"黑洞"吞噬(不存储任何数据)
    • 仅记录 SQLbinlog(可用于复制架构)
  • 适用场景
    • 主从复制中的中继节点(过滤数据)
    • SQL 语法测试、审计日志转发

7. NDB / NDBCLUSTER(MySQL Cluster)

  • 特点:
    • 分布式、内存优先的高可用引擎
    • 自动分片、无共享架构(shared-nothing)
    • 支持 ACID、毫秒级故障切换
  • 适用场景
    • 电信计费、实时风控、高可用关键系统
    • 需要横向扩展和 99.999% 可用性的场景

二、索引

2.1 什么是索引?

假设新华字典中的字都是无序的,想象一下你在新华字典中查询一个字,却没有字典目录,你只能一页一页地翻找字典,寻找目标文字,可能翻了几个小时甚至几天才能找到。这显然效率极低。在数据库世界中,索引 (Index)就是那页"字典目录"。它是一种专门用于加速数据检索的数据结构,能让数据库引擎在海量数据中快速定位目标记录,而无序扫描整张表。

2.2 索引的分类

不同的存储引擎有不同的索引类型和实现:

  • 按照数据结构分类:

    • B+数索引 :采用 B+数的数据结构(MySQLInnoDB存储引擎默认采用的索引方式)
    • Hash 索引 :采用哈希表的数据结构(仅Memory存储引擎支持)
  • 按照物料存储方式分类:

    • 聚簇索引(也可以称聚集索引):索引和表中的数据在一起,数据存储的时候就是按照索引顺序存储的。一张表只能有一个聚簇索引。
    • 非聚簇索引(也可以称非聚集索引):索引和表中数据是分开的,索引是独立于表空间的,一张表可以有多个非聚簇索引
  • 按照字段特性分类:

    • 主键索引 :特殊的唯一索引,InnoDB 中即聚簇索引
    • 唯一索引 :确保字段值唯一(UNIQUE),InnoDB 中即非聚簇索引(二级索引)
    • 普通索引:最基本的索引类型,同样属于非聚簇索引(二级索引),叶子节点存的是主键值
    • 全文索引 :用于对大文本字段(如 CHAR, VARCHAR, TEXT)进行全文搜索
  • 按照字段个数分类:

    • 单列索引:对单个字段建立索引
    • 多列索引(也称复合索引):对多个字段组合建立索引,遵循最左前缀原则

2.3 索引的操作

主键自动添加索引

对于主键字段,会自动添加索引,无需自己创建。主键字段上的索引称为主键索引,在 InnoDB 引擎中,主键索引也是聚簇索引。

Unique 约束的字段自动添加索引

对于添加了UNIQUE约束的字段,会自动添加索引,无需自己创建,字段上的索引称为唯一索引。在 InnoDB 中,唯一索引是非聚簇索引(二级索引),其叶子节点存储的是主键值(用于回表查询)。

给指定字段添加索引

  • 建表时添加索引:
sql 复制代码
CREATE TABLE 表名(
    定义的字段,

    --为某个字段添加索引
    INDEX 索引名(字段名)
)

--示例
CREATE TABLE STUDENT(
    ID INT PRIMARY KEY AUTO_INCREMENT,
    NAME VARCHAR(255),
    AGE INT,

    -- 为NAME字段创建索引
    INDEX INEDX_NAME(NAME)
);
  • 表已经建好,后续给字段添加索引
sql 复制代码
ALTER TABLE 表名 ADD INDEX 索引名(字段名);

--示例
ALTER TABLE STUDENT ADD INDEX INDEX_NAME(NAME);

或者

sql 复制代码
CREATE INDEX 索引名 ON 表名(字段名);

--示例
CREATE INDEX INDEX_NAME ON STUDENT(NAME);
  • 查看表上的索引
sql 复制代码
SHOW INDEX FROM 表名;

-- 示例
SHOW INDEX FROM STUDENT;

或者

sql 复制代码
SHOW KEYS FROM 表名;

--示例
SHOW KEYS FROM STUDENT;
Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment Visible Expression
STUDENT 0 PRIMARY 1 ID A 0 BTREE YES
STUDENT 1 INEDX_NAME 1 NAME A 0 YES BTREE YES

字段说明:

  • Table:表名

  • Non_unique:是否允许重复值

    • 0 → 唯一索引(包括主键)
    • 1 → 非唯一索引
  • Key_name:索引名称

    • 主键索引固定为 PRIMARY
    • 其他索引为你创建时指定的名字(如 idx_name),或自动生成(如外键约束生成的索引)
  • Seq_in_index:该列在多列索引(复合索引)中的顺序(从 1 开始)

    • 示例:
      CREATE INDEX idx_name_age ON student(name, age);
      则 name 的 Seq_in_index = 1,age 的 Seq_in_index = 2
  • Column_name:构成该索引的列名

  • Collation:索引中列的排序方式

    • A → 升序(Ascending)
    • D → 降序(Descending,MySQL 8.0+ 支持)
    • NULL → 不排序(如哈希索引)
  • Cardinality:索引列的基数(即不同值的数量)的估算值(重要性:这是判断索引是否有效的核心指标!)

  • 值越大 → 区分度越高 → 索引效率越高

  • 值越小(接近1)→ 区分度低 → 索引可能被优化器忽略

  • Sub_part:是否对列的前缀部分建立索引

    • NULL → 整列索引
    • 数字(如 20)→ 只索引该列的前 20 个字符(常见于 VARCHAR/TEXT)
      CREATE INDEX idx_email_prefix ON student(email(20));,这个索引的Sub_part = 20
  • Packed:索引是否被压缩(如使用 PACK_KEYS 表选项)

    • 通常为 NULL,很少使用,可忽略
  • Null:该列是否允许 NULL 值(NULL 值会影响索引效率(B+树需特殊处理),建议尽量将索引列设为 NOT NULL)

    • YES → 允许 NULL
    • 空→ 不允许 NULL
  • Index_type:索引使用的数据结构类型

    • BTREE → 最常见(InnoDB 默认)
    • HASH → Memory 引擎支持
    • FULLTEXT / SPATIAL → 特殊索引
  • Comment:索引的额外注释(一般为空)

    • 用途:某些存储引擎(如 MyISAM)可能用到
  • Index_comment:用户创建索引时添加的注释(MySQL 5.7+ 支持)
    CREATE INDEX INDEX_NAME ON STUDENT(name) COMMENT '用于按姓名查询学生';

  • 删除索引
sql 复制代码
ALTER TABLE 表名 DROP INDEX 索引名;

--示例
ALTER TABLE STUDENT DROP INDEX INDEX_NAME;

2.4 MySQL 索引采用了 B+树数据结构

MySQLInnoDB 存储引擎(默认引擎)使用 B+树作为索引的数据结构,为了了解为什么使用 B+树,我们需要了解几种树的数据结构

  • 二叉树
  • 红黑树
  • B 树
  • B+ 树

区别:树的高度不同。树的高度越低,性能越高。这是因为每一个节点都是一次磁盘I/O。磁盘I/O是性能开销大的操作。

2.4.1 二叉树

有一张表:

ID NAME
401 林晓峰
205 陈雨桐
347 赵明远
119 黄子涵
682 郭文轩
901 周静怡
456 吴志豪
677 孙雅婷
213 徐瑞阳
555 王小东

如果不给 ID 字段添加索引,默认进行全表扫描,假设查询 ID=555 的数据,那至少需要进行 10 次磁盘 I/O。效率低。如果给 ID 字段添加索引,假设 ID 索引又使用了二叉树 这种数据结构,那么生成的二叉树将会是如下这样(采用左小右大的方式进行构建树结构):

那现在如果查询 ID=555 的数据,至少需要 4 次磁盘 I/O。效率更好了。

但二叉树在数据极端情况下,效率极低。比如这张表:

ID NAME
1 林晓峰
2 陈雨桐
3 赵明远
4 黄子涵
5 郭文轩
6 周静怡
7 吴志豪
8 孙雅婷
9 徐瑞阳
10 王小东

最终构建的二叉树就会退化为一个链表,假设查询ID=10的数据,那么至少需要10次磁盘I/O操作。效率低。所以MySQL的索引没有采用这种数据结构。

2.4.2 红黑树(自平衡二叉树)

有一张表:

ID NAME
1 林晓峰
2 陈雨桐
3 赵明远
4 黄子涵
5 郭文轩
6 周静怡
7 吴志豪
8 孙雅婷
9 徐瑞阳
10 王小东

如果给 ID 字段添加索引,假设 ID 索引又使用了红黑树这种数据结构,那么生成的红黑树将会是如下这样:

假设查询ID=10的数据,那么至少需要5次磁盘I/O操作。相比二叉树,性能有所提升。

红黑树是通过自旋平衡规则进行旋转,子节点会自动分叉为2个分支,从而减少树的高度,当数据有序插入时比二叉树数据检索性能更好。

但如果数据量很大,比如100万条数据,log₂(1000000) ≈ 20,那么树的高度大约是20层,效率依然很低,所以MySQL的索引没有采用这种数据结构。

2.4.3 B树

B树首先是一个自平衡的。 B树每个节点下的子节点数量 > 2。 B树每个节点中也不是存储单个数据,可以存储多个数据。 B树又称为平衡多路查找树
B树分支的数量不是2,是大于2,具体是多少个分支,由阶决定。例如:

  • m阶的B树,一个节点下最多有m个节点,每个节点中最多有m-1个数据
  • 3阶:一个节点下最多有3个子节点,每个节点中最多有2个数据。
  • 4阶:一个节点下最多有4个子节点,每个节点中最多有3个数据。
  • 5阶(5, 4)
  • 6阶(6, 5)
  • ...
  • 16阶(16, 15)【MySQL采用了16阶】

有一张表:

ID NAME
1 林晓峰
2 陈雨桐
3 赵明远
4 黄子涵
5 郭文轩
6 周静怡
7 吴志豪
8 孙雅婷
9 徐瑞阳
10 王小东

当阶数m=3时,构建的B树结构如下:

当查询 ID=10的数据时,只需要3次磁盘I/O

当采用B树存储索引时,更加详细存储结构上这样的。

B树中,每个节点不仅存储了索引值 ,还存储了所以在对应的数据行 ,每个

节点中的p1、p2、p3指针指向下一个节点。每个节点存储索引值和对应的数据行这就是聚簇索引

当阶数m=16时,并且有100万条数据:

  • 第 0 层(根):最多 15 个 key
  • 第 1 层:最多 16 个节点,每个 15 key → 共 16 × 15
  • 第 2 层:16² 个节点 → 16² × 15 个 key
  • ...
  • 第 h 层(叶子层):16ʰ 个节点 → 16ʰ × 15 个 key
    最大 key 数 = 15 × ( 1 + 16 + 16 2 + ⋯ + 16 h ) = 15 × 16 h + 1 − 1 16 − 1 = 16 h + 1 − 1 \text{最大 key 数} = 15 \times (1 + 16 + 16^2 + \dots + 16^h) = 15 \times \frac{16^{h+1} - 1}{16 - 1} = 16^{h+1} - 1 最大 key 数=15×(1+16+162+⋯+16h)=15×16−116h+1−1=16h+1−1
    令其 ≥ 1,000,000:
    16 h + 1 − 1 ≥ 10 6 ⇒ 16 h + 1 ≥ 1 , 000 , 001 16^{h+1} - 1 \geq 10^6 \\ \Rightarrow 16^{h+1} \geq 1,000,001 16h+1−1≥106⇒16h+1≥1,000,001

取对数:

h + 1 ≥ log ⁡ 16 ( 1 , 000 , 001 ) h+1 \geq \log_{16}(1,000,001) h+1≥log16(1,000,001)

换底公式:

log ⁡ 16 ( 10 6 ) = log ⁡ 10 ( 10 6 ) log ⁡ 10 ( 16 ) = 6 log ⁡ 10 ( 16 ) ≈ 6 1.2041 ≈ 4.983 \log_{16}(10^6) = \frac{\log_{10}(10^6)}{\log_{10}(16)} = \frac{6}{\log_{10}(16)} \approx \frac{6}{1.2041} \approx 4.983 log16(106)=log10(16)log10(106)=log10(16)6≈1.20416≈4.983

所以:对应 层数 = h + 1 = 5 层

所以使用B数存储100万数据,最多需要5次磁盘I/O

B树数据结构存在的缺点是:不适合做区间查找,对于区间查找效率较低。假设要查ID[2~7]之间的,需要查找的是2,3,4,5,6,7。那么查这每个索引值都需要从头节点开始搜索。会有很多的磁盘I/O操作。所以MySQL的索引不采用B树数据结构。而B+树解决了区间查找问题。

2.4.4 B+树

B+树相较于B树改进了以下几点:

  • B+树将数据都存储在叶子节点,并且叶子节点之间使用双向链表进行连接,这样就很适合范围查找。
  • B+树的非叶子节点上只有索引值,没有数据,所以非叶子节点可以存储更多的索引值,这样可以让B+树更矮更胖,提供检索效率。(因为非叶子节点不需要存储实际数据行(如 VARCHAR、TEXT 等大字段),每个节点的固定大小(如 InnoDB 默认 16KB)可以容纳更多键值-指针对)

有一张表:

ID NAME
1 林晓峰
2 陈雨桐
3 赵明远
4 黄子涵
5 郭文轩
6 周静怡
7 吴志豪
8 孙雅婷
9 徐瑞阳
10 王小东

当阶数m=3时,构建的B+树结构如下:

当查询 ID=10的数据时,只需要4次磁盘I/O

当采用B+树存储索引时,且更加详细存储结构上这样的。

MySQL为什么选择B+树作为索引的数据结构,而不是B树?

  • 非叶子节点上可以存储更多的键值,阶数可以更大,更矮更胖,磁盘I/O次数少,数据查询效率高
  • 所有数据都是有序存储在叶子节点上,让范围查找,分组查找效率更高
  • 数据页之间、数据记录之间采用链表链接,让升序降序更加方便操作。

如果一张表没有主键索引,那还会创建B+树吗?

答案:当一张表没有主键索引时,会,InnoDB 仍然会创建 B+ 树------但它是基于一个"隐式生成的聚簇索引"(hidden clustered index)构建的。

规则:

  1. 如果定义了主键(PRIMARY KEY) → 使用主键作为聚簇索引。
  2. 如果没有主键,但有 NOT NULL 且唯一的索引(UNIQUE INDEX) → 选用第一个这样的列作为聚簇索引。
  3. 如果以上都没有 → InnoDB 自动创建一个隐藏的 6 字节 ROW_ID 列,并以此作为聚簇索引。

这个隐藏的ROW_ID 是单调递增的整数,对用户不可见(无法通过 SQL 查询到),仅内部使用。

2.5 索引类型及核心优化机制

2.5.1 Hash索引

Hash索引是基于哈希表(Hash Table)实现的。

  • 只支持 等值查询(=、IN),不支持范围查询(>、<、BETWEEN)、排序(ORDER BY)或最左前缀匹配
  • 查找时间复杂度为 O(1)(理想情况下)
  • Memory 引擎:原生支持显式创建 Hash 索引。
  • InnoDB 引擎:不支持用户显式创建 Hash 索引,但会自适应地在内存中为热点索引页构建自适应哈希索引(Adaptive Hash Index, AHI),以加速等值查询;

原理如下:

有一张表:

ID NAME AGE
1 林晓峰 18
2 林一霖 19
3 陈雨桐 20

假设给NAME字段上添加了Hash索引,并且构建的Hash表如下:

当查询NAME="陈雨桐"。通过Hash算法将"陈雨桐"转为数组下标,再通过下标找到链表,然后在链表中遍历找到"陈雨桐"的行指针,就找到这行数据。

注意:不同的字符串,经过哈希算法得到的数组下标可能相同,这叫做哈希碰撞/哈希冲突。好的哈希算法应该具有很低的碰撞概率。常用的哈希算法如MD5SHA-1SHA-256等都被设计为尽可能减少碰撞的发生。

Hash索引优缺点:

优点:

  • 只能用在等值比较中,效率很高

缺点:

  • 无法用于排序、范围扫描
  • 哈希冲突可能降低性能
  • 不适用于高重复值字段(如性别)

2.5.2 聚簇索引和非聚簇索引

按照数据的物理存储方式不同,可以将索引分为聚簇索引 (聚集索引)和非聚簇索引(非聚集索引)。

聚簇索引

  • 每个表有且仅有一个聚簇索引(因为数据只能按一种物理顺序存储)。
  • InnoDB 中,主键就是聚簇索引。如果没有主键,InnoDB 会用隐藏 ROW_ID 构建聚簇索引。
  • 数据行与索引存储在一起:叶子节点直接包含完整的行数据。

聚簇索引的原理图:(B+树,叶子节点上存储了索引值 + 数据)

优点:

  • 数据与索引一体存储:聚簇索引的叶子节点直接包含整行数据,因此通过聚簇索引查找可以直接返回完整记录,无需额外回表操作。
  • 范围查询高效:因为数据按聚簇索引的顺序物理存储,范围扫描(如 WHERE id BETWEEN 10 AND 100)非常快,只需顺序读取磁盘页。
  • 主键查询性能最优:主键通常是聚簇索引,因此主键等值查询(WHERE id = ?)速度极快。
  • 减少 I/O 次数:对于按聚簇索引顺序访问的数据,可以利用磁盘局部性原理,提升缓存命中率。

缺点:

  • 每个表只能有一个聚簇索引:因为数据只能按一种物理顺序存储,无法同时"聚簇"在多个字段上。
  • 插入/更新可能引起页分裂:如果插入的数据不是按聚簇索引顺序(如使用 UUID 作主键),会导致频繁的页分裂和碎片,降低写入性能。
  • 主键设计不当影响性能:若主键过长(如 VARCHAR(255)),会增大所有二级索引的体积(因为二级索引叶子节点存储的是主键值)。
  • 不适合频繁更新的列作为聚簇索引:更新聚簇索引列的值会导致整行数据在物理上移动(或重建),开销大。即对数据进行修改或删除时需要更新索引树,会增加系统的开销。

非聚簇索引

  • 一张表可有多个聚簇索引
  • 索引与数据分开存储:叶子节点只存索引列值 + 主键值(InnoDB 中)
  • InnoDB 中的所有二级索引都是非聚簇索引。
  • MyISAM 中任意字段上的索引都是非聚簇索引。
  • 查询时若需要其他字段,需通过主键回表到聚簇索引中查找完整数据。

非聚簇索引的原理图:(B+树,叶子节点上存储了索引值 + 行指针)

优点:

  • 可创建多个:一个表可以有多个非聚簇索引,用于加速不同查询条件(如 name, email, created_at 等)。
  • 不影响数据物理存储顺序:非聚簇索引是独立结构,不会干扰表的数据组织。
  • 支持覆盖索引(Covering Index):如果查询的所有字段都包含在非聚簇索引中(即"索引覆盖"),无需回表,性能接近聚簇索引。
  • 适合高选择性字段:对于唯一或接近唯一的字段(如手机号、邮箱),非聚簇索引能快速定位。

缺点:

  • 需要回表(Lookup):大多数情况下,非聚簇索引只存储主键值,要获取完整数据必须再查一次聚簇索引(即"回表"),增加 I/O
  • 占用额外存储空间:每个非聚簇索引都是一棵独立的 B+ 树,会消耗内存和磁盘空间。
  • 维护成本高:每次 INSERT/UPDATE/DELETE 操作都可能需要更新多个非聚簇索引,影响写性能。
  • 对范围查询效率较低(相比聚簇索引):虽然也能做范围扫描,但若需回表,性能不如聚簇索引的顺序读。

2.5.3 二级索引

二级索引也属于非聚簇索引。也有人把二级索引称为辅助索引。

有一个表STUDENTID是主键。AGE是非主键。在AGE字段上添加的索引称为二级索引。(所有非主键索引都是二级索引)

ID NAME AGE
1 林晓峰 21
2 陈雨桐 32
3 赵明远 42
4 黄子涵 39
5 郭文轩 41
6 周静怡 27
7 吴志豪 30

二级索引数据结构:

二级索引的查询原理:

例如:查询AGE=30的学生信息

sql 复制代码
SELECT * FROM STUDNET WHERE AGE = 30;
  1. AGE索引树中找到AGE=30的节点,得到了主键值7
  2. 再根据主键值,去主键索引树中,找到这条数据信息。这叫做回表

为什么会"回表"?

因为使用了SELECT *。避免"回表【回到原数据表】"是提高SQL执行效率的手段。

例如:SELECT ID FROM STUDENT WHERE AGE = 30; 这样的SQL语句是不需要回表的,直接就获得了ID值。

2.5.4 单列索引(单一索引)

单列索引,指针对数据库表中的单个字段(单列) 创建的索引,其作用是对该列的数据实现高效的快速查找与排序,能够有效提升数据库的查询响应速度,优化数据库整体检索性能。

举个例子,假设存在一张学生数据表(STUDENT),表中包含字段:学生编号(ID)、姓名(NAME)、年龄(AGE)、性别(GENDER)。

若我们为该表的「学生编号(ID)」列创建单列索引,那么所有以该字段作为查询条件或排序依据的操作,都会得到性能优化。例如执行如下 SQL 查询语句:

sql 复制代码
SELECT * FROM STUDNET WHERE ID = 1;

在建立单列索引的前提下,数据库会直接通过索引快速匹配定位到 ID =1 的数据行,避免了全表扫描的低效检索方式,以此显著加快查询速度。

2.5.5 多列索引(复合索引)

多列索引(Multi-Column Index)也被称作复合索引(Compound Index),指的是基于数据库表中的两个及以上字段共同创建的索引。

与单列索引仅包含单个字段不同,多列索引会将多个列的字段值组合在一起作为索引键,核心作用是大幅提升多字段组合条件查询的检索效率。

举个例子,假设我们有一张订单表(Order),表中包含的字段有:订单编号(OrderID)、客户编号(CustomerID)、订单日期(OrderDate)和订单金额(OrderAmount)。

如果我们为该表的「客户编号」与「订单日期」两个字段创建多列索引 (CustomerID, OrderDate),那么当查询条件中同时包含这两个字段时,数据库就能通过该索引快速定位到匹配的记录。

例如执行如下 SQL 查询语句:

sql 复制代码
SELECT * FROM `Order` WHERE CustomerID = 1001 AND OrderDate = '2023-02-01';

因为提前创建了对应的多列索引 ,数据库可直接通过该索引精准筛选出符合条件的订单记录,无需进行全表扫描,进而显著提升查询速度。

需要注意的是,多列索引 在提升查询效率的同时,也会带来一定的使用成本:创建和维护多列索引会占用更多的存储空间,同时对数据表的新增、修改、删除等写操作的执行效率,会产生轻微的影响。

相较于单列索引,多列索引具备以下优势:

  • 减少索引数量:一个多列索引可包含多个查询常用字段,能有效减少数据表中创建的索引总数,降低索引的存储空间占用和日常维护成本。
  • 提升多条件查询性能:当查询语句的筛选条件中,包含多列索引的相关字段时,数据库可直接通过多列索引完成高效的条件匹配与数据过滤,大幅提升查询效率。
  • 实现覆盖查询:如果多列索引中包含了查询所需的全部字段,数据库可直接从索引中提取返回结果,无需回表查询数据表的原始数据,这也是性能最优的查询方式。
  • 优化排序与分组操作 :多列索引本身是按字段组合排序存储的,当查询中涉及基于索引字段的排序(ORDER BY)或分组(GROUP BY)操作时,可直接使用索引完成排序和分组,无需额外处理,提升操作效率。

2.5.6 覆盖索引

当查询所需的所有字段都包含在某个索引的列中时,数据库可以直接从索引中返回结果,无需回表。这种索引就称为覆盖索引

当使用覆盖索引时,MySQL可以直接通过索引,也就是索引上的数据来获取所需的结果,而不必再去查找表中的数据。这样可以显著提高查询性能。

假设有一个用户表(USER)有以下列:ID,NAME,AGE,EMAIL

需求:根据NAME查询用户的EMAIL。如果为了提高这个查询的性能,可以创建一个多列索引 ,包含(NAME,EMAIL)这两列。使用查询语句时,只查询包含在索引列中的字段,此时这个多列索引就成为了覆盖索引覆盖索引 是从使用角度来称呼的。

ID NAME AGE EMAIL
1 Alice 20 Alice@qq.com
2 Bob 21 Bob@qq.com
3 Cindy 22 Cindy@qq.com
4 Tom 23 Tom@qq.com

创建覆盖索引:

sql 复制代码
CREATE INDEX INDEX_NAME_EMAIL ON USER(NAME,EMAIL);

当执行以下查询时:

sql 复制代码
-- ✅ 覆盖索引:查询字段都在索引中
SELECT EMAIL FROM USER WHERE NAME = 'Tom';

可以直接从索引中读取 (NAME, EMAIL),不需要回表访问数据行

sql 复制代码
-- ❌ 不是覆盖索引:需要 age 字段,不在索引中
SELECT AGE FROM USER WHERE NAME = 'Bob';

必须通过 ID=2 回表查找 AGE → 需要额外 I/O

MySQL可以直接使用覆盖索引 (INDEX_NAME_EMAIL)来获取EMAIL,而不必再去查找用户表中的数据。这样可以减少磁盘I/O并提高查询效率。而如果没有覆盖索引,MySQL会先使用索引(NAME)来找到匹配的行,然后再回表查询获取EMAIL,这个过程会增加更多的磁盘I/O和查询时间。

值得注意的是,覆盖索引的创建需要考虑查询的字段选择。如果查询需要的字段较多,可能需要创建包含更多列的覆盖索引,以满足完全覆盖查询的需要。

覆盖索引的优点:

  1. 提升查询性能

    覆盖索引包含查询所需的所有字段,数据库无需回表(即访问主表数据行),可直接从索引中返回结果。这避免了额外的磁盘 I/O 和数据页加载,显著加快查询速度。

  2. 减少 I/O 与内存开销

    由于无需读取完整的数据行,系统在磁盘读取和内存缓存方面的压力大幅降低,尤其在高频查询或大数据量场景下效果明显。

  3. 降低网络传输量(适用于分布式/远程数据库)

    当查询结果完全由索引提供时,传输的数据量更小,有助于减少网络带宽消耗,提升响应效率(尤其在客户端-服务器架构中)。

  4. 减轻系统负载,提升稳定性

    在高并发或资源受限的环境中,减少回表操作可有效降低 CPUI/O 和锁竞争等系统开销,从而增强数据库的整体可靠性和可维护性。

覆盖索引的缺点:

  1. 占用更多存储空间

    覆盖索引需包含多个列的数据,导致索引体积显著增大,可能消耗大量磁盘空间,尤其在宽表或大字段(如 TEXT、VARCHAR 长字符串)被纳入索引时更为明显。

  2. 增加写操作成本

    索引越大,对 INSERTUPDATEDELETE 操作的影响越显著------每次数据变更都需同步更新庞大的索引结构,可能拖慢写入性能。

  3. 使用条件受限

    仅当查询的 SELECT 列和 WHERE 条件中的字段全部被索引覆盖时,才能触发覆盖索引。若查询涉及未包含的列,数据库仍需回表,无法享受性能优势。


💡 最佳实践建议

覆盖索引并非"越多越好"。应结合高频查询模式,选择性地为关键查询路径设计精简、高效的覆盖索引,平衡读写性能与存储成本。

2.5.7 索引下推

索引下推(Index Condition Pushdown,简称 ICP)是 MySQL 数据库中针对索引查询的重要性能优化手段。该优化的核心逻辑是:将查询语句中的部分过滤条件,直接下推到数据库的「索引检索层级」提前执行过滤,以此减少无效的回表次数,大幅优化查询的整体性能。

具体来说,在未开启索引下推时,MySQL 仅通过索引快速匹配到符合索引前置条件的记录后,就会立刻通过主键回表读取完整的数据行,再在内存中对剩余的过滤条件进行筛选;而开启索引下推优化后,MySQL 会在索引的叶子节点层级就执行相关的过滤条件,提前过滤掉不满足条件的索引记录,只将真正符合全部条件的记录主键返回,再基于这些有效主键进行回表操作。这种方式能避免对无效数据的回表读取,减少磁盘 I/O 开销,从而缩短查询耗时。

索引下推的优化逻辑通常基于多列(复合)索引生效,单列索引场景下几乎没有该优化的适用空间。

假设有如下 USER 用户表结构及数据:

ID NAME AGE CITY
1 吕木云 27 上海
2 黄芸欢 21 深圳
3 王芸茜 29 北京
4 黄文旺 30 云南
5 陈馨薇 28 深圳

为该表创建多列索引:

sql 复制代码
ALTER TABLE USER ADD INDEX IDX_NAME_CITY_AGE (NAME, CITY, AGE);

举个应用案例,需求为:查询所在城市是 深圳年龄大于 25 岁的用户信息,对应的查询语句为:

sql 复制代码
SELECT * FROM USER WHERE CITY = '深圳' AND AGE > 25;

如果仅为 AGE 字段创建单列索引,该场景无法触发索引下推优化。传统的查询方式中,数据库会先通过年龄索引读取所有满足 age > 25 的记录并完成回表,再在内存中逐一筛选出城市为 深圳 的数据,这个过程会产生大量无效的回表 I/O 操作。

而基于上述的多列索引 IDX_NAME_CITY_AGE (NAME, CITY, AGE) 开启索引下推后,查询的执行逻辑发生了本质优化:数据库在扫描多列索引的过程中,会在索引层级就同时校验 CITY = '深圳'AGE > 25 两个过滤条件,只筛选出同时满足双条件的索引记录。

只有通过索引层级过滤后的有效记录,才会被执行回表操作,读取数据表中的完整行数据,最终返回结果。

这种优化方式的核心价值,就是通过索引层的前置过滤,减少了不必要的磁盘 I/O 和数据传输,从根源上提升了多条件查询的执行效率。

  • 索引下推的核心特点:不是取消回表,而是减少回表的次数;该优化和覆盖索引是两种不同的性能优化思路,覆盖索引是「彻底避免回表」,索引下推是「减少无效回表」。
  • 索引下推是「查询执行阶段」的优化策略,覆盖索引是「索引使用角度」的最优状态,多列索引是「索引创建角度」的类型;三者结合使用时,能实现 MySQL 查询性能的最大化优化,也是实际业务中最常用的索引优化组合。

2.6 索引优缺点

索引是数据库中用于优化数据检索效率的重要数据结构,能够快速定位目标数据,有效加速查询操作,其优缺点如下:

优点:

  • 提升查询性能:通过索引快速定位数据,减少查询扫描的数据量,大幅加快数据检索速度。
  • 优化排序效率:借助索引的有序存储特性,可快速完成字段的排序与分组,降低排序的资源消耗。
  • 减少磁盘 I/O :避免全表扫描的大量磁盘读写,减少磁盘 I/O 次数,提升数据读取效率。

缺点:

  • 占用额外存储空间:索引作为独立数据结构,会占用一定的物理存储,数据量越大、索引越多,空间占用越明显。
  • 损耗写操作性能:执行插入、更新、删除操作时,需同步更新索引,增加操作耗时,降低写操作效率。
  • 消耗系统资源 :索引的检索与维护会占用内存、CPU 资源,高并发场景下可能影响数据库整体性能。

2.7 索引的适用场景和不适用场景

索引是数据库性能优化的核心手段,但并非所有场景都适用。需结合表的数据量、读写频率、业务需求等因素综合判断,具体的适用与不适用场景如下:

建议创建索引的场景:

  1. 高频查询的字段:
    对于经常出现在 WHERE 条件、JOIN 关联条件中的字段,创建索引能大幅减少数据库的扫描范围,直接定位目标数据,显著提升查询响应速度。例如用户表的 ID、订单表的 NO 这类高频查询字段,非常适合建立索引。
  2. 数据量大的表(大表):
    当数据表的记录数达到数万甚至数十万级别时,全表扫描的耗时会急剧增加。此时为查询字段建立索引,可避免全表扫描的低效操作,快速定位所需数据,优化效果会非常明显。而小表的全表扫描本身耗时极短,索引的优化收益有限。
  3. 需排序 / 分组的字段:
    对于经常出现在 ORDER BYGROUP BY 子句中的字段,创建索引能利用索引的有序存储特性,避免数据库执行额外的排序计算,直接通过索引完成排序或分组操作,大幅降低资源消耗。
  4. 键关联字段:
    在多表关联查询(如 JOIN 操作)中,外键字段是表与表之间的关联纽带。为外键字段建立索引,能加速表之间的关联匹配过程,避免关联时的全表扫描,提升多表查询的整体效率。
  5. 唯一性高的字段:
    字段的唯一性越高,索引的筛选效率就越强。例如 ID手机号 这类唯一性 接近 100% 的字段,索引能快速匹配到单条记录;反之,唯一性低的字段索引效果会大打折扣。

不建议创建索引的场景:

  1. 高频更新的表或字段
    对频繁执行 INSERT、UPDATE、DELETE 操作的表或字段,不建议创建过多索引。因为每次写操作不仅要修改数据表的原始数据,还需要同步更新所有关联的索引结构,这会显著增加写操作的耗时,降低业务的写入性能。
  2. 数据量极小的表(小表)
    对于仅包含数百、数千条记录的小表,全表扫描的时间成本极低,索引带来的查询性能提升几乎可以忽略不计。此时创建索引反而会额外占用存储空间,增加数据库的维护负担。
  3. 唯一性极差的字段
    对于性别(仅男 / 女)、状态(仅启用 / 禁用)这类取值范围极小、唯一性极差的字段,不建议创建索引。这类字段的索引筛选效果差,查询时大概率仍需扫描索引的大部分数据,甚至索引文件的体积会超过数据表本身,既浪费存储空间,又无法有效提升查询效率。
  4. 查询结果占比极高的字段
    如果查询语句需要返回表中 30% 以上的记录,使用索引的效率反而不如全表扫描。因为此时数据库需要频繁地在索引和数据表之间切换读取,产生大量额外 I/O 开销。

总结:索引的核心本质是「以空间换时间」,即用索引占用的额外存储空间,换取查询效率的大幅提升;实际应用中需合理创建索引,平衡查询性能与写操作效率,避免过度建索引或建无效索引。

相关推荐
さかた ぎんとき882 小时前
从SQL到磁盘的Mysql全链路解析
数据库·sql·mysql
それども2 小时前
数据库读写分离和事务的关系
数据库
TGITCIC2 小时前
2026数据分析Agent最新落地方向解析
数据库·数据分析·ai大模型·ai智能体·ai数据·ai问数·ai sql
luoluoal2 小时前
基于python的基于深度学习的车俩特征分析系(源码+文档)
python·mysql·django·毕业设计·源码
wangqiaowq2 小时前
SQL Server 对非聚簇索引的 INCLUDE 列数量和大小有限制
数据库
Coder_Boy_2 小时前
基于SpringAI的在线考试系统-核心业务流程图
java·数据库·spring boot·软件工程
lkbhua莱克瓦242 小时前
进阶-InnoDB引擎-后台线程
开发语言·mysql·innodb
小毕超2 小时前
基于 Qwen Code Skills 实践构建自定义数据分析智能体
mysql·skills·qwen code
松涛和鸣3 小时前
DAY49 DS18B20 Single-Wire Digital Temperature Acquisition
linux·服务器·网络·数据库·html