返回首页

10| 索引原理与类型

适用版本:MySQL 8.0.x

一、为什么要学索引

在 MySQL 调优里,索引几乎是绕不开的主题。很多初学者对索引的第一印象是:“加了索引,查询就会变快。” 这句话方向没错,但并不完整。更准确地说,索引是一种帮助 MySQL 更快定位数据的数据结构,它的核心价值在于:

  • 减少需要扫描的数据量
  • 降低磁盘 I/O 成本
  • 提升排序、分组、连接等操作效率
  • 帮助优化器选择更优执行路径

但索引并不是越多越好。索引会额外占用磁盘空间,也会增加 INSERTUPDATEDELETE 的维护成本。因此,学习索引,不能停留在“会建索引”,而要理解它的底层原理、类型差异和适用场景。


二、索引到底是什么

可以把索引理解成一本书的目录。

  • 没有目录时,要从第一页翻到最后一页找内容
  • 有了目录后,可以先定位章节,再快速找到具体页码

数据库也是一样:

  • 没有索引:MySQL 可能需要扫描整张表,即全表扫描
  • 有索引:MySQL 可以先在索引中找到目标位置,再读取对应记录

需要注意的是,索引本身也是要存储的,它不是“魔法加速器”,而是一种以空间换时间的设计。


三、MySQL 中索引的核心工作原理

在 MySQL 8.0 中,最常见的存储引擎是 InnoDB。日常开发里提到的索引,大多数时候都是指 InnoDB 的索引结构

1. 索引本质上是有序数据结构

索引不是简单的“字段标记”,而是按一定规则组织起来的数据结构。MySQL 可以利用这种有序性进行:

  • 等值查找
  • 范围查找
  • 排序优化
  • 分组优化
  • 部分覆盖查询

2. 索引与数据的关系

在 InnoDB 中,表数据和索引关系非常紧密:

  • 主键索引(聚簇索引):叶子节点直接保存整行数据
  • 普通二级索引:叶子节点保存索引列值 + 主键值

这意味着:

  • 用主键查询时,通常能直接拿到整行
  • 用二级索引查询时,可能还需要根据主键再回到聚簇索引中查一次完整记录,这个过程常称为回表

3. 索引提高了读性能,但会影响写性能

每次插入、更新、删除数据时,MySQL 不仅要改数据,还要维护相关索引。因此索引越多,写入成本通常越高。

所以索引设计的核心平衡是:

既要让高频查询足够快,也要避免给写入带来不必要的负担。


四、索引的常见分类

MySQL 中索引可以从多个角度分类。为了不混淆,我们按“用途”和“底层实现”两个角度来理解。

1. 按约束和用途分类

(1)主键索引(PRIMARY KEY)

主键索引用于唯一标识一行数据。

特点:

  • 值必须唯一
  • 不能为 NULL
  • InnoDB 中主键索引就是聚簇索引
  • 一张表只能有一个主键

示例:

CREATE TABLE user_info (
    id BIGINT PRIMARY KEY,
    name VARCHAR(100),
    phone VARCHAR(20)
) ENGINE=InnoDB;

(2)唯一索引(UNIQUE INDEX)

唯一索引要求索引列值不能重复,但通常允许 NULL(具体表现与业务设计相关,建议实际验证)。

示例:

CREATE TABLE account (
    id BIGINT PRIMARY KEY,
    email VARCHAR(100) UNIQUE,
    nickname VARCHAR(50)
) ENGINE=InnoDB;

适合场景:

  • 邮箱
  • 身份证号
  • 订单号
  • 用户名

(3)普通索引(INDEX)

最常见的索引类型,不强制唯一,只是为了提升查询效率。

CREATE INDEX idx_name ON user_info(name);

(4)联合索引(复合索引)

一个索引包含多个列。

CREATE INDEX idx_age_city ON user_info(age, city);

它常用于:

  • 多条件过滤
  • 避免多次回表
  • 优化排序和分组

联合索引是高频面试点,也是实际调优中非常重要的一类,下一篇会重点展开。

(5)前缀索引

对于字符串字段,如果完整索引太长,可以只索引前几个字符。

CREATE INDEX idx_email_prefix ON account(email(10));

优点:

  • 节省索引空间
  • 降低维护成本

缺点:

  • 区分度可能下降
  • 某些场景下无法完全覆盖查询
  • 对排序和精确过滤能力会受影响

(6)全文索引(FULLTEXT)

全文索引用于文本检索,不适合替代普通 B+Tree 索引。

CREATE TABLE article (
    id BIGINT PRIMARY KEY,
    title VARCHAR(200),
    content TEXT,
    FULLTEXT KEY idx_ft_content(content)
) ENGINE=InnoDB;

适合场景:

  • 关键词搜索
  • 自然语言检索

不适合:

  • 普通等值查询
  • 范围查询

(7)空间索引(SPATIAL)

空间索引用于 GIS、地图坐标、地理位置等空间数据场景,普通业务表中相对少见。


2. 按底层数据结构分类

(1)B+Tree 索引

这是 MySQL InnoDB 最常用、最重要的索引结构。

适合:

  • 等值查询
  • 范围查询
  • 排序
  • 分组
  • 前缀匹配

绝大多数业务索引,底层都是 B+Tree。

(2)Hash 索引

Hash 索引更擅长等值查询,不适合范围查询和排序。

需要注意:

  • InnoDB 并不直接把普通用户索引实现为 Hash 索引
  • Memory 引擎支持显式 Hash 索引
  • InnoDB 中有自适应哈希索引(Adaptive Hash Index),但它是引擎自动优化机制,不等同于我们手工创建的 Hash 索引

(3)R-Tree / 空间结构

主要服务于空间索引场景,一般业务开发接触较少。


五、InnoDB 中两类最关键的索引

理解 InnoDB,最重要的是分清楚:

  • 聚簇索引
  • 二级索引

1. 聚簇索引

聚簇索引并不是一种“额外索引类型”,而是数据存储方式

在 InnoDB 中:

  • 表数据按主键顺序组织
  • 聚簇索引的叶子节点就是整行数据

这也是为什么主键设计很重要:

  • 主键过长,会让二级索引也变大
  • 主键频繁变更,代价很高
  • 随机主键可能导致页分裂更频繁

通常建议:

  • 使用简短、稳定、递增的主键
  • 常见做法是 BIGINT 自增主键或业务上有序的雪花 ID

2. 二级索引

除主键索引以外的其他索引,一般都可以视作二级索引。

二级索引叶子节点保存:

  • 索引列值
  • 对应主键值

当查询只需要索引中的列时,可以直接返回结果,这叫覆盖索引;如果还要读取其他不在索引中的列,就需要回表。

示例:

CREATE TABLE orders (
    id BIGINT PRIMARY KEY,
    user_id BIGINT NOT NULL,
    status TINYINT NOT NULL,
    amount DECIMAL(10,2) NOT NULL,
    created_at DATETIME NOT NULL,
    INDEX idx_user_id(user_id)
) ENGINE=InnoDB;

执行:

SELECT id, user_id FROM orders WHERE user_id = 1001;

因为 idx_user_id 的叶子节点里本来就有:

  • user_id
  • id(主键值)

所以该查询可能直接走覆盖索引。

而如果执行:

SELECT * FROM orders WHERE user_id = 1001;

就往往需要回表读取完整行。


六、查看索引的常用工具

理解索引,离不开“会看”。下面是 MySQL 中最常用的几个工具。

1. SHOW INDEX

查看表上的索引信息:

SHOW INDEX FROM orders;

重点字段:

  • Key_name:索引名
  • Column_name:索引列
  • Seq_in_index:列在联合索引中的顺序
  • Non_unique:是否允许重复
  • Cardinality:基数,粗略反映区分度

2. SHOW CREATE TABLE

查看建表语句及索引定义:

SHOW CREATE TABLE orders;

适合快速确认:

  • 主键
  • 唯一约束
  • 联合索引
  • 前缀长度
  • 排序方向

3. INFORMATION_SCHEMA.STATISTICS

用系统表查询索引元信息:

SELECT
    TABLE_SCHEMA,
    TABLE_NAME,
    INDEX_NAME,
    COLUMN_NAME,
    SEQ_IN_INDEX,
    NON_UNIQUE
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = 'demo_db'
  AND TABLE_NAME = 'orders';

适合批量排查、脚本化巡检。

4. EXPLAIN

判断 SQL 是否真正用了索引:

EXPLAIN SELECT * FROM orders WHERE user_id = 1001;

EXPLAIN 会在第三篇里专门展开,但在索引学习阶段,至少要先养成一个习惯:

建了索引,不等于一定被用到。执行前后都要看执行计划。


七、从零开始看一个索引示例

下面通过一个小例子感受索引的作用。

1. 建表

CREATE TABLE employees (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    emp_no VARCHAR(20) NOT NULL,
    name VARCHAR(100) NOT NULL,
    dept_id INT NOT NULL,
    hire_date DATE NOT NULL,
    salary DECIMAL(10,2) NOT NULL,
    INDEX idx_emp_no(emp_no),
    INDEX idx_dept_id(dept_id)
) ENGINE=InnoDB;

2. 按工号查询

SELECT * FROM employees WHERE emp_no = 'E10086';

如果 emp_no 上有索引,MySQL 就不需要扫描整张表,而是可以通过 idx_emp_no 快速定位记录。

3. 按部门查询

SELECT * FROM employees WHERE dept_id = 10;

由于 dept_id 上也有索引,筛选特定部门时效率会高很多,尤其是在大表中更明显。

4. 没有索引的字段查询

SELECT * FROM employees WHERE salary = 15000.00;

如果 salary 没有索引,这类查询就可能触发全表扫描。

这也说明,索引设计要围绕实际查询模式来做,而不是见列就建。


八、索引并不是总能生效

很多慢 SQL 的根源,不是“没建索引”,而是“建了但没用上”。常见原因包括:

1. 查询条件对索引列做了函数或运算

SELECT * FROM orders WHERE DATE(created_at) = '2026-06-01';

这里对 created_at 做了函数计算,通常会影响索引使用。

更好的写法:

SELECT * FROM orders
WHERE created_at >= '2026-06-01 00:00:00'
  AND created_at < '2026-06-02 00:00:00';

2. 隐式类型转换

SELECT * FROM user_info WHERE phone = 13800138000;

如果 phone 是字符串类型,但查询写成数字,可能导致优化器无法按预期使用索引。

3. 匹配范围过大

如果筛选结果占全表很大比例,优化器可能认为全表扫描更划算,而不是走索引。

4. LIKE 以通配符开头

SELECT * FROM article WHERE title LIKE '%MySQL';

这类查询通常无法有效使用普通 B+Tree 索引。

而下面这种更容易利用索引:

SELECT * FROM article WHERE title LIKE 'MySQL%';

九、索引设计的常见优化建议

这一部分是落地最常用的经验总结。

1. 给高频查询条件建索引

优先考虑以下列:

  • WHERE 中高频过滤列
  • JOIN 关联列
  • ORDER BY
  • GROUP BY

2. 优先选择区分度高的列

区分度越高,筛选能力越强,索引价值越大。

例如:

  • user_id 通常区分度高
  • gender 只有男/女两个值,区分度低,单独建索引往往意义不大

3. 不要滥建索引

每多一个索引,就多一份维护成本。尤其是写多读少的表,更要控制索引数量。

4. 主键尽量短、稳定、递增

原因包括:

  • 聚簇索引叶子节点存整行数据,主键会影响数据组织
  • 二级索引叶子节点保存主键值,主键越大,二级索引越大
  • 主键更新代价高

5. 尽量使用覆盖索引

如果查询的列都在索引中,就能减少回表,提高性能。

例如:

CREATE INDEX idx_dept_hire ON employees(dept_id, hire_date);

查询:

SELECT dept_id, hire_date
FROM employees
WHERE dept_id = 10;

这类查询就更容易成为覆盖索引。

6. 用联合索引替代多个低效单列索引

如果查询经常是:

WHERE dept_id = ? AND hire_date = ?

那么 INDEX(dept_id, hire_date) 往往比两个单列索引更实用。


十、学习索引时容易混淆的几个点

1. “有索引”不等于“查询一定快”

还要看:

  • SQL 写法是否合理
  • 返回行数是否过多
  • 是否发生回表
  • 是否需要排序/临时表
  • 优化器是否选择了该索引

2. “建得越多越好”是误区

索引过多会带来:

  • 更多磁盘占用
  • 更慢的写入
  • 更高的维护和变更成本
  • 优化器选择变复杂

3. “唯一索引一定比普通索引快很多”并不准确

唯一索引主要解决的是约束唯一性问题,性能差异要结合具体场景看,不能简单绝对化。

4. “索引就是给查询加速”也不完整

索引除了帮助过滤,还能帮助:

  • 排序
  • 分组
  • 连接
  • 覆盖读取

十一、一个实用的排查思路

当你怀疑某条 SQL 需要索引时,可以按下面顺序检查:

  1. 这条 SQL 的过滤条件是什么?
  2. 这些列是否已有索引?
  3. 索引顺序是否匹配查询模式?
  4. 是否发生函数计算、隐式转换、前导模糊匹配?
  5. EXPLAIN 是否真的用了索引?
  6. 即使使用索引,扫描行数是否仍然过大?
  7. 是否可以改成覆盖索引或联合索引?

这个思路很适合日常排查慢 SQL。


十二、小结

这篇文章主要解决了三个基础问题:

  1. 索引是什么:本质是帮助 MySQL 快速定位数据的数据结构
  2. 索引有哪些类型:主键索引、唯一索引、普通索引、联合索引、前缀索引、全文索引、空间索引等
  3. InnoDB 中怎么理解索引:重点掌握聚簇索引、二级索引、回表、覆盖索引

如果只记住一句话,我建议记住这一句:

索引设计不是“给字段贴标签”,而是围绕查询路径组织数据。

下一篇我们继续深入,重点理解 MySQL 中最核心的索引结构:B+Tree 与联合索引。把这部分吃透之后,再看慢 SQL 和执行计划,思路会清晰很多。


📝 版权声明:本文为原创技术博客,转载请注明出处。

如文章中存在错误或不准确之处,欢迎在评论区指正,感谢您的阅读与支持!

上一篇

09|聚合函数与分组统计

下一篇

11 | B+Tree 与联合索引