一、索引
1.1 什么是索引?
快速定位数据的一种存储结构,比如文章目录;设计思想:以空间换时间。
1.2 索引的种类
常见的索引分类:
|--------|-------------------|--------------|-------------|----------------|
| 按数据结构分 | B+tree索引 | Hash索引 | Full-text索引 | |
| 物理存储 | 聚集索引 | 非聚集索引 | | |
| 字段特性 | 主键索引(PRIMARY KEY) | 唯一索引(UNIQUE) | 普通索引(INDEX) | 全文索引(FULLTEXT) |
| 字段个数 | 单列索引 | 联合索引(复合、组合) | | |
常见的索引数据结构和区别:
- 二叉树、红黑树、B树、B+树
- 区别:树的高度影响获取数据的性能(每一个树节点都是一次磁盘I/O)
1.3 B+tree索引
1.3.1 二叉树
特点:一个节点最多两个子节点,小的在左边,大的在右边

如果数据按顺序进入:
形成一个链表结构,树的高度很高,元素的查找效率等于链表查询O(n),数据检索效率低下。

1.3.2 红黑二叉树(平衡二叉树)
通过自旋平衡,减少树的高度。但是数据过多时,节点个数就越多,树的高度也会增高,影响查询效率。

1.3.3 B-树
特点:
B树的节点可以有多个子节点,多叉树;
一个节点可以存储多个元素;
平衡多路查找树;
不适合范围查找
3阶B树
1.3.4 B+树
B+树结构图
对B-树的优化(为什么MySql用B+树):
- 所有的数据存储在叶子结点上(最下面一行),排序、分组、去重查询更简单
- 非叶子结点没有放数据,可以存更多的键值,树会更矮更胖,查询效率更快
- 每一个非叶子节点上都有对应的双向指针,范围查询、排序友好
如果一个表没有主键索引还会创建B+树吗?
答案
↓
↓
↓
↓
↓
↓
↓
↓
↓
↓
↓
会的,InnoDB是MySQL的一种存储引擎,会为每个表创建一个主键索引。如果表没有一个明确的主键索引,InnoDB会使用一个隐藏的、自动生成的主键创建索引,使用的是B+树结构。
1.4 Hash索引
InonoDB不支持显式创建Hash索引,只支持自适应Hash索引;
Memory引擎支持Hash索引

Hash索引底层原理 类似与java的HashMap的原理


1.5 聚集索引与非聚集索引

按物理存储分类:InnoDB存储方式是聚集索引
聚集索引 :索引和数据存在一起 (叶子节点存整行数据)

MyISAM是非聚集索引
非聚集索引 :索引和数据分开存 (叶子节点只存主键值)

1.5.1 二级索引

非主键索引都是二级索引,也是非聚集索引

1.5.2 覆盖索引

已经在age索引中查到了id,节省了回表步骤,查询更快
这种索引列覆盖了查询字段就是覆盖索引
1.5.3 索引下推
索引下推 (Index Condition Pushdown, ICP),是MySQL5.6针对 扫描二级索引 的优化,用来在范围查询时减少回表 的次数,适用于MyISAM和InnoDB

查询 :SELECT * FROM user WHERE name LIKE '张%' AND age = 20;
1. 无索引下推
- 引擎按
name LIKE '张%'找到 100 条索引记录(含主键)。 - 全部 100 次回表,读取整行数据。
- 返回 Server 层,再过滤
age = 20,只剩 10 条。
- 浪费 90 次回表 I/O。
2. 开启索引下推
- 引擎按
name LIKE '张%'遍历索引。 - 在索引叶子节点直接判断
age = 20。 - 只把符合条件的 10 条主键传给 Server。
- 仅 10 次回表,效率显著提升。
1.6 单列、组合索引
1.6.1 单列索引
单列索引 :只对一个字段建索引
sql
CREATE INDEX idx_name ON user(name);
1.6.2 联合索引(复合索引)
把多个字段按顺序组合成一个索引。
sql
CREATE INDEX idx_name_age_sex ON user(name, age, sex);
name → age → sex 按顺序排序

核心规则:最左前缀原则
联合索引,必须从左往右数,连续不断才能用;跳过左边的列,右侧列全部失效;范围查询会截断右侧列。
例如索引 (a,b,c):
- 能用到:
where a=? - 能用到:
where a=? and b=? - 能用到:
where a=? and b=? and c=? - 用不到:
where b=? - 用不到:
where a=? and c=?(b 断了)
1.6.3 单列索引与联合索引的区别
| 对比维度 | 单列索引 (Single-Column Index) | 联合索引 (Composite/Combined Index) |
|---|---|---|
| 索引结构 | 仅对单个字段构建 B + 树索引 | 对多个字段按顺序组合构建 B + 树索引 |
| 生效规则 | 仅该字段作为查询条件时生效 | 遵循最左前缀原则,从最左列开始的连续字段组合才能生效 |
| 多条件查询效率 | 多条件时 MySQL 通常仅能选择一个索引使用,无法同时利用多个单列索引过滤 | 可利用多字段连续过滤,一次索引扫描完成多条件筛选,效率远高于多个单列索引 |
| 回表次数 | 多条件查询时,仅用一个索引过滤,剩余条件需回表后筛选,回表次数多 | 多字段联合过滤,精准定位数据,回表次数大幅减少(覆盖索引可完全避免回表) |
| 空间占用 | 每个索引是独立 B + 树,多个单列索引总空间占用大 | 一个索引树包含多列,总空间占用远小于多个单列索引 |
| 写入性能(增删改) | 每新增 / 修改 / 删除数据,需更新所有相关单列索引,索引越多写入越慢 | 仅需更新一个联合索引,写入性能优于多个单列索引 |
| 维护成本 | 索引数量多,维护、优化、排查成本高 | 索引数量少,结构清晰,维护成本低 |
| 适用查询场景 | 1. 仅按单个字段高频查询2. 字段区分度极高(如手机号、身份证号)3. 多字段查询不固定,无法形成固定组合 | 1. 多字段固定组合高频查询2. 遵循最左前缀,可覆盖单个 / 多个连续字段的查询3. 排序、分组场景(ORDER BY/GROUP BY) |
| 典型 SQL 示例 | CREATE INDEX idx_name ON user(name);``SELECT * FROM user WHERE name = '张三'; |
CREATE INDEX idx_name_age_city ON user(name, age, city);``SELECT * FROM user WHERE name = '张三' AND age = 20 AND city = '北京'; |
| 核心优势 | 简单灵活,适配单字段查询,无需考虑字段顺序 | 多条件查询性能高,空间占用小,写入性能优,可实现覆盖索引 |
| 核心劣势 | 多条件查询性能差,空间浪费,写入性能差 | 需严格遵循最左前缀,字段顺序设计错误会导致索引失效 |
1.7 索引的优缺点及使用场景
1.7.1、优点
- 提高检索效率
- 降低排序成本,索引对应的字段有自动排序功能,默认升序
1.7.2 缺点
- 创建和维护索引 随数据量增加维护成本增加
- 占用额外磁盘空间,数据量越大,占用空间越大
- 降低写入性能(增删改):每插入 / 更新 / 删除数据,必须同步更新所有相关索引树,索引越多,写入越慢
1.7.3 索引的使用场景
适合场景:
- 经常出现在查询条件中的字段,尤其是大表,必须加索引
不适合场景:
- 更新非常频繁的字段
- 区分度极低的字段:如性别(男 / 女)、状态(0/1),索引过滤性差,加了反而拖慢性能
- 几乎不查询的字段:仅存储、不参与查询的字段,完全没必要加索引
二、优化
2.1、优化方法

2.2 通过Explain干预执行计划
1、Explain含义
EXPLAIN 是 MySQL 的执行计划分析工具 ,可以查看 MySQL 将要如何执行一条 SQL,可以帮助我们发现查询瓶颈,优化查询性能
2、Explain作用
核心作用
- 看这条 SQL 走没走索引
- 看是全表扫描 还是索引查找
- 看有没有回表、文件排序、临时表等性能问题
- 用来优化慢查询
3、Explain用法
例如:
sql
Explain Extended select * from users;
结果:

执行结果关键字段(必懂)
| 字段 | 含义(重点) |
|---|---|
| id | 查询执行顺序,越大越先执行 |
| select_type | 查询类型(简单查询 / 子查询 / 联合查询) |
| table | 涉及哪张表 |
| type | 最重要:访问效率(从好到坏:system > const > eq_ref > ref > range > index > ALL) |
| possible_keys | 可能用到的索引 |
| key | 实际用到的索引 |
| key_len | 索引使用长度 |
| ref | 与索引比较的列 |
| rows | 预计扫描行数 |
| Extra | 额外重要信息(Using index、Using filesort、Using temporary 等) |
最关键的三个判断点
-
type = ALL→ 全表扫描,性能最差,必须优化
-
key = NULL→ 索引没生效,白建了
-
Extra 出现这些要警惕
- Using index:覆盖索引,很好
- Using filesort:文件排序,性能差
- Using temporary:用到临时表,性能差
- Using index condition:索引下推,正常优化
3.1 id 列
查询中执行 SELECT 子句的顺序编号
- id 越大,越先执行
- id 相同,从上到下顺序执行
- id 为 NULL,最后执行(一般是 UNION RESULT)
3.2 select_type 返回列详解
| select_type | 含义 | 出现场景 | 性能评价 |
|---|---|---|---|
| SIMPLE | 简单查询 | 不包含子查询、UNION 的普通 SELECT | 最好,最常见 |
| PRIMARY | 主查询 | 包含子查询 / UNION 时,最外层的查询 | 正常 |
| SUBQUERY | 普通子查询 | SELECT / WHERE 里的子查询,不依赖外层表 | 一般,可能被优化掉 |
| DEPENDENT SUBQUERY | 相关子查询 | 子查询依赖外层表的结果,需逐行执行 | 很差,性能灾难 |
| DERIVED | 衍生表 | FROM 里的子查询,会生成临时表 | 一般,数据大就慢 |
| UNCACHEABLE SUBQUERY | 不可缓存子查询 | 子查询结果无法缓存,每行都要重算 | 极差 |
| UNION | UNION 中的第二个及以后查询 | UNION 连接的多个查询 | 正常 |
| UNION RESULT | UNION 结果集 | UNION 合并结果的临时表 | 正常 |
| DEPENDENT UNION | 相关 UNION | UNION 里的查询依赖外层结果 | 差,和 dependent subquery 一样 |
| MATERIALIZED | 物化子查询 | MySQL 把子查询结果存成临时表 | 比 dependent 好很多 |
1)SIMPLE
最简单的查询,没有子查询、没有 union。
SELECT * FROM user WHERE id = 1;
2)PRIMARY
当 SQL 里有子查询或 UNION 时,最外层查询就是 PRIMARY。
3)SUBQUERY
WHERE / SELECT 里的子查询,不依赖外层表:
SELECT * FROM user
WHERE id = (SELECT id FROM user_info WHERE name = 'a');
4)DEPENDENT SUBQUERY(重点坑)
子查询用到了外层表的字段 ,必须循环执行,性能极差:
SELECT * FROM user u
WHERE EXISTS (SELECT 1 FROM order o WHERE o.user_id = u.id);
外层查一行,子查询就要执行一次,数据多直接卡死。
5)DERIVED
FROM 括号里的子查询,会生成临时表:
SELECT * FROM (SELECT * FROM user WHERE age>18) t;
6)UNION & UNION RESULT
-
UNION:第二个及以后的查询
-
UNION RESULT:合并结果
SELECT id FROM a
UNION
SELECT id FROM b;
7)MATERIALIZED
MySQL 5.6+ 对子查询的优化,把子查询结果物化成临时表,避免重复执行。比 DEPENDENT SUBQUERY 好很多。
3.3 type列
效率从高到低:system > const > eq_ref > ref > fulltext >ref_or_null > range > index > ALL ,一般保证到range,最好到ref
| type | 级别 | 含义(白话) | 出现场景 | 性能评价 |
|---|---|---|---|---|
| system | 最优 | 系统表,只有 1 行数据 | 系统表、极少出现 | 顶级,几乎不用关心 |
| const | 极优 | 主键 / 唯一索引等值匹配,最多 1 行 | where id=1(id 主键) |
极快,理想状态 |
| eq_ref | 优秀 | 多表 join,关联字段是主键 / 唯一索引 | join 关联主键 | 非常快,最优 join |
| ref | 良好 | 普通索引等值匹配,可能多行 | where name='xxx'(普通索引) |
性能不错,常用 |
| range | 一般 | 索引范围查询 | between、>、<、in、like | 能接受,需注意优化 |
| index | 较差 | 遍历整个索引树 | 查索引全部,但不扫全表 | 比全表好,但依然慢 |
| ALL | 最差 | 全表扫描 | 无索引、索引失效 | 必须优化 |
3.4 key_len 列
MySQL 实际使用了索引的字节长度
MySQL 字段长度计算规则(必须知道)
1)常见字段占用字节
- tinyint:1
- int:4
- bigint:8
- char(n):n × 字符集字节(utf8mb4=4)
- varchar(n) :n × 字符集字节 + 2 字节长度
- date:3
- datetime:8
- timestamp:4
2)额外开销
- 允许 NULL:+1 字节
- 变长字段(varchar):+2 字节
例如:
sql
explain
select * from orders where user_id = 1;

user_id 是int类型,占4个字节,所以key_len=4
3.5ref 列
当前查询中,与索引列做等值匹配的对象是什么(是常量?还是某个字段?还是函数?)。
常见取值
| ref 值 | 含义 | 出现场景 |
|---|---|---|
| const | 与常量等值匹配 | where name = '张三' |
字段名 (如 db.t.id) |
与另一张表的字段关联匹配 | JOIN 关联查询 |
| func | 与函数 / 表达式匹配 | 索引失效前兆 |
| NULL | 没有等值匹配 | 范围查询、全表扫描、索引未使用 |
举例
1) ref = const(最常见、最健康)
select * from user where name = '张三';
- name 是索引
- 与常量比较
- ref = const
2) ref = 表。字段(JOIN 关联)
select * from user u
join order o on u.id = o.user_id;
3) ref = NULL
- 范围查询:
where id > 100 - 模糊查询:
where name like '张%' - 没走索引、全表扫描这些情况 ref 都是 NULL
4)ref = func(基本代表索引废了)
select * from user where left(name,1) = '张';
- 对索引列使用了函数
- MySQL 识别为和函数结果比较
- ref = func → 索引大概率失效