%%
# 纲要
> 主干纲要、Hint/线索/路标
# Q&A
#### 已明确
#### 待明确
> 当下仍存有的疑惑
**❓<font color="#c0504d"> 有什么问题?</font>**
%%
# MySQL 索引分类
MySQL 中索引类型,可按不同维度如下划分:
| 分类标准 | 类型 |
| ---- | ------------------------------------------------------ |
| 数据结构 | **B+树索引**、**Hash 索引**、**倒排索引** |
| 物理存储 | **聚簇索引**、**二级索引** |
| 字段性质 | **主键索引**、**唯一索引**、**普通索引**、**Full-Text 全文索引**、**前缀索引** |
| 字段数量 | **单列索引**、**联合索引** |
![[Excalidraw/Excalidraw-Solution/MySQL 索引.excalidraw.md#^group=ny4J_cVeLcZ3XkCyliYj5|630]]
> [!NOTE] 不同引擎在 "索引" 上支持的数据结构
> ![[_attachment/02-开发笔记/08-数据库/MySQL 相关/MySQL 索引.assets/IMG-MySQL 索引-366C6E7F8CB28DA48AE42F8A00D4ABEF.png|544]]
<br><br>
# 「聚簇索引」与「二级索引」
- "**聚簇索引**" 也称 "**主键索引**";
- "**二级索引**" 也称 "**辅助索引**",包括 **普通索引**、**唯一索引**、**前缀索引** 这几种具体类型。
![[02-开发笔记/08-数据库/MySQL 相关/MySQL InnoDB 引擎#^5kf4d8]]
参见 [[02-开发笔记/08-数据库/MySQL 相关/MySQL InnoDB 引擎#InnoDB 中的 B+树索引|MySQL InnoDB 引擎#B+树索引]]
<br><br><br>
# 主键索引
也即 "**聚簇索引**",建立在**主键**字段上的索引,只能在 **==建表时创建==**。
![[02-开发笔记/08-数据库/MySQL 相关/MySQL InnoDB 引擎#^8luk98]]
<br><br><br>
# 唯一索引(Unique Index)
建立在 **`UNIQUE` 字段**上的索引,一张表可以有**多个**唯一索引。
> [!example] 创建唯一索引
>
> ```sql
> -- 建表时创建
> CREATE TABLE my_table (
> ...
> UNIQUE KEY(col1, col2, ...)
> );
>
> -- 建表后创建
> CREATE UNIQUE INDEX idx_name
> ON my_table(col1, col2, ...);
> ```
>
<br><br><br>
# 普通索引(Index)
普通索引就是建立在 **普通字段** 上的索引,用作索引的字段 **既不要求为主键,也不要求为 `UNIQUE`**。
> [!example] 创建普通索引
>
> ```sql
> -- 建表时创建
> CREATE TABLE my_table (
> ...
> INDEX(col1, col2, ...)
> );
>
> -- 建表后创建
> CREATE INDEX idx_name
> ON my_table(col1, col2, ...);
> ```
>
<br><br><br>
# 前缀索引(Prefix Index)
前缀索引:是指**只对字符类型字段的==前 N 个字符==建立索引**,而非对整个字段建立索引,从而减少索引存储空间,提高索引效率。
前缀索引可建立在 `char`、`varchar`、`text`、`binary`、`varbinary` 类型字段上。
> [!info] **前缀索引的存储方式与普通 B+ 树索引相同**,但索引值仅存储**列的前 N 个字符**
> [!example] 创建前缀索引
>
> ```sql
> -- 建表时创建
> CREATE TABLE users (
> id INT PRIMARY KEY,
> email VARCHAR(255),
> INDEX idx_email_prefix (email(10)) -- 只索引前10个字符
> ) ENGINE=InnoDB;
>
> -- 建表后创建
> CREATE INDEX idx_name
> ON my_table(col(n)); -- 索引col列的前n个字符
> ```
>
<br><br><br>
# 全文索引(Full-text Index)
> 仅 InnoDB (MySQL 5.6+)与 MyISAM 引擎支持,底层通过 "**倒排索引**" 实现。
适用场景:**快速==全文搜索==**(模糊匹配)——通过 `MATCH() ... AGAINST()` 语法搜索**长文本字段**,如 `CHAR`、`VARCHAR`、`TEXT`。
> [!example] 创建全文索引
>
> ```sql
> -- 建表时创建
> CREATE TABLE articles (
> id INT AUTO_INCREMENT PRIMARY KEY,
> title VARCHAR(255),
> tag VARCHAR(255),
> content TEXT,
> FULLTEXT INDEX idx_ft_title (title) -- 对title列创建全文索引
> FULLTEXT INDEX idx_ft_title_tag (title, tag) -- 联合全文索引
> );
>
> -- 建表后创建
> -- 方式一:
> ALTER TABLE articles ADD FULLTEXT INDEX idx_ft_title (title);
> -- 方式二:
> CREATE FULLTEXT INDEX idx_ft_title ON articles(title);
> ```
![[02-开发笔记/08-数据库/MySQL 相关/MySQL InnoDB 引擎#^l3if7m]]
<br><br><br>
# 哈希索引(Hash Index)
> 仅 Memory 存储引擎支持,基于**哈希表**进行查找。
适用于**等值查询**,**不能用于范围查询**,如 `BETWEEN`、`LIKE`、`>`、`<` 等操作。
<br><br><br>
# 索引使用建议
## 索引建立
适用索引的情况:
- `UNIQUE` 字段
- 常用于 `WHERE` 查询的字段 ——可考虑联合索引
- 常用于 `GROUP BY` 或 `ORDER BY` 的字段 ——索引表遍历
不适用索引的情况:
- **频繁修改**的字段——维护索引开销大
- 存在**大量重复值**的字段——例如性别。
索引建立注意事项:
- **主键索引**最好是**自增**的 ——数据插入时直接追加,**无页分裂**,页满时直接**追加新页**,**不涉及数据移动**。。
- 索引列最好为 **`NOT NULL`**
<br>
## 索引查询
- 尽可能触发 **覆盖索引**,避免回表。
- 遵循联合索引的 **最左匹配原则**
![[02-开发笔记/08-数据库/MySQL 相关/MySQL InnoDB 引擎#覆盖索引(Covering Index)|MySQL InnoDB 引擎]]
![[02-开发笔记/08-数据库/MySQL 相关/MySQL InnoDB 引擎#联合索引的 "最左匹配原则"|MySQL InnoDB 引擎]]
<br><br>
## 索引失效情况
###### (1)
- (1)**避免查询中对 "索引列" 进行==计算==、调用==函数==或隐式==类型转换==**,均会导致索引失效。
- 原因:索引保存的是**字段原始值**,无法根据计算后的值进行比对,故无法走索引
- 优化: MySQL 8.0 起,增加了函数索引,可**针对==函数计算后的值==建立索引**。
###### (2)
- (2)**避免 `OR` 条件中包含 "非索引列"**
- 原因:`OR` 中包含非索引列时,索引将失效。
- 优化:为查询列建立联合索引,或者分别建索引。
> [!example]
>
> ```sql
> -- name有索引, 而city无索引, 则将走全表扫描
> SELECT * FROM users WHERE name = 'Alice' or city = 'New York';
> ```
>
###### (3)
- (3)**避免 `LIKE %xx` 或 `LIKE %xx%`**: `%` 号在前,则将进行 "**全表扫描**" (type=ALL) 或 "**==全索引扫描==**" (type=index)[^2]
- 原因:B+树按索引 key 排序,只能根据前缀进行比较。
- 优化:=> 改用 **`LIKE xxx%`** 或建立 `FULLTEXT` 索引
> [!example]
>
> ```sql
> -- '%'在前,索引失效
> SELECT * FROM users WHERE name LIKE '%Alice%';
>
> -- 优化一:
> SELECT * FROM users WHERE name LIKE 'Alice%'; -- 可以使用索引
> -- 可改用全文索引
> ALTER TABLE users ADD FULLTEXT INDEX idx_name (name);
> SELECT * FROM users WHERE MATCH(name) AGAINST('Alice');
> ```
>
>
<br><br><br>
# 执行计划检查
通过 `EXPLAIN` + `SQL` 语句可查看 SQL 的执行计划,判断**是否触发使用索引**。
> [!example]
>
> ```sql
> EXPLAIN SELECT * FROM users WHERE name = 'Alice';
> ```
>
>
执行计划的显示结果包括:
| 字段 | 说明 |
| ------------- | ----------------------------------- |
| possible_keys | 可能使用的索引 |
| key | **实际使用**的索引,若为 **NULL 表示未使用索引** |
| key_len | **索引列的字段类型的字节数**(若允许为 NULL,则字节数需+1) |
| rows | **预计扫描的数据行数** |
| type | **数据扫描类型** |
| Extra | **额外的查询优化信息** |
## type 字段说明
type 字段指示了**查找数据时采用的扫描方式**,包括以下类型(按**查询效率**由低到高排列):
| 字段值 | 类型 | 触发索引 | 说明 |
| ------ | ------------------------------- | ---- | ------------------------------------------------------------------------- |
| ALL | **全表**扫描 | ❌ | 索引未生效,遍历整个数据表。 |
| index | **全索引**扫描 | ✔️ | 索引表进行全表扫描。 |
| range | **索引范围**扫描 | ✔️ | 索引定位后,还需进行**范围查找** (where 中使用 `<`, `>`, `between` 时) |
| ref | **非唯一索引**扫描 <br>(或唯一索引的非唯一性前缀) | ✔️ | 索引列的**值不唯一**,即普通索引。 <br>因此索引定位后,还需在目标值附近进行小范围检索 |
| eq_ref | **唯一索引**扫描 | ✔️ | 连接查询时,通过主键或唯一索引查找。 <br>(例如 `join` 条件为两表的某**列相等**,且**该列具有唯一索引**) |
| const | **主键 or 唯一索引扫描** | ✔️ | 使用主键或唯一索引与常量值比较,只有一条查询结果 <br>(例如 `SELECT name FROM users WHERE id = 1;`) |
> [!NOTE] 区分 "**全表扫描`ALL`**" 与 "**全索引扫描`index`**"
>
> - ALL:遍历**完整数据表**(按聚簇索引顺序),**会读取每一行的完整数据**,磁盘 I/O 开销更大。
> - index:只遍历**特定索引表**,根据 **==特定索引列==的索引顺序**,遍历所有记录(而非原表存储顺序,即聚簇索引顺序)
>
> index 通常出现在下列情况:
>
> - **`ORDER BY` 子句**,对**索引列**排序时。
> - 例如 `SELECT age FROM users ORDER BY age;`, `age` 为索引列。
> - **覆盖索引**,索引列本身包含所需字段,**只需读取==索引列==的数据**,不访问原表完整数据行,无回表过程,磁盘 I/O 开销更小。
> - 例如 `SELECT age FROM users;`, `age` 为索引列。
>
<br>
## Extra 字段说明
Extra 字段提供了额外的查询优化信息,帮助判断:
- 是否索引覆盖、是否需要回表查询;
- 是否需要**排序** 或 **临时表**;
| 内容 | 含义 | 使用索引 |
| --------------------- | ---------------------------------------- | ---- |
| Using index | **覆盖索引**(无需回表) | ✔️ |
| Using index condition | **索引条件优化**,仍需回表,但**减少了回表次数** | ✔️ |
| Using where | **索引过滤**,**需要回表** | ✔️ |
| Using filesort | 需要**额外排序**(例如查询包含 group by,且无法利用索引完成排序时) | ❌ |
| Using temporary | 需要使用**临时表**(常见于涉及 order by 或 group by 时) | ❌ |
> [!NOTE] 「索引过滤」:指基于**索引**完成了数据过滤,但**仍需==回表==**取得完整数据。
>
> 即 where 子句**使用了索引列**,但 **SELECT 查询数据** 超出索引字段,故需要回表的情况。
>
> 例如 `SELECT * FROM users WHERE name = 'Alice';`
>
<br><br><br>
# Buffer
## 闪念
> sudden idea
## 候选资料
> Read it later
# ♾️参考资料
- [为什么 MySQL 采用 B+ 树作为索引? | 小林coding](https://xiaolincoding.com/mysql/index/why_index_chose_bpuls_tree.html#_3%E3%80%81%E8%8C%83%E5%9B%B4%E6%9F%A5%E8%AF%A2)
- [索引失效](https://mp.weixin.qq.com/s/lEx6iRRP3MbwJ82Xwp675w)
# Footnotes
[^1]: [为什么 MySQL 采用 B+ 树作为索引? | 小林coding](https://xiaolincoding.com/mysql/index/why_index_chose_bpuls_tree.html#mysql-%E4%B8%AD%E7%9A%84-b-%E6%A0%91)
[^2]: [MySQL 使用 like “%x“,索引一定会失效吗? | 小林coding](https://xiaolincoding.com/mysql/index/index_issue.html#%E9%A2%98%E7%9B%AE%E4%BA%8C)