MySQL存储引擎与索引优化
一、MySQL 体系结构
MySQL 整体分为四层架构:
1 | ┌─────────────────────────────────────────────┐ |
各层说明
| 层级 | 功能 | 核心组件 |
|---|---|---|
| 连接层 | 处理客户端连接、授权认证、权限验证 | 连接池、认证模块 |
| 服务层 | SQL解析、优化、执行,跨引擎功能实现 | 解析器、优化器、执行器、缓存 |
| 引擎层 | 数据的存储和提取,不同引擎功能各异 | InnoDB、MyISAM、Memory 等 |
| 存储层 | 数据持久化到文件系统 | 数据文件、日志文件 |
二、存储引擎
2.1 常见存储引擎对比
MySQL 支持多种存储引擎,常用的有:
| 特性 | InnoDB | MyISAM | MEMORY | NDB |
|---|---|---|---|---|
| 事务支持 | 支持 | 不支持 | 不支持 | 支持 |
| 行级锁 | 支持 | 不支持(表锁) | 表锁 | 行级锁 |
| 外键 | 支持 | 不支持 | 不支持 | 支持(有限) |
| 崩溃恢复 | 支持(Redo Log) | 不支持 | 不支持 | 支持 |
| 全文索引 | 5.6+支持 | 支持 | 不支持 | 不支持 |
| 数据缓存 | 缓冲池 | 索引缓存 | 内存存储 | 内存存储 |
| 适用场景 | 高并发、事务型应用 | 读多写少、日志分析 | 临时表、缓存 | 分布式集群 |
2.2 InnoDB 存储引擎(推荐)
特点:
- 支持事务(ACID)
- 支持行级锁,并发性能好
- 支持外键约束
- 支持崩溃恢复(Redo Log + Undo Log)
- 使用聚簇索引,数据与主键索引存储在一起
1 | -- 查看当前默认存储引擎 |
2.3 MyISAM 存储引擎
特点:
- 不支持事务
- 表级锁,并发写入性能差
- 查询速度快,适合读多写少场景
- 支持全文索引
- 数据文件(.MYD)和索引文件(.MYI)分开存储
1 | -- MyISAM 适用场景:日志表、统计表 |
2.4 存储引擎选择建议
| 场景 | 推荐引擎 | 原因 |
|---|---|---|
| 电商订单、金融交易 | InnoDB | 需要事务支持 |
| 用户系统、权限管理 | InnoDB | 需要外键约束 |
| 日志记录、访问统计 | MyISAM / InnoDB | 读多写少,或需要事务 |
| 临时数据、会话缓存 | MEMORY | 内存存储,速度快 |
| 分布式数据库集群 | NDB | MySQL Cluster |
三、索引概述
3.1 索引的作用
索引是帮助 MySQL 高效获取数据的数据结构。
优点:
- 通过索引列对数据进行排序,降低数据库的排序成本
- 大幅降低数据检索的 I/O 成本
- 加速表与表之间的连接(JOIN)
- 加速 GROUP BY 和 ORDER BY 操作
缺点:
- 索引提高了查询效率,但降低了 INSERT、DELETE、UPDATE 的效率
- 索引需要占用额外的存储空间
- 过多的索引会增加优化器选择索引的时间
1 | -- 查看表的索引 |
3.2 索引的数据结构
MySQL 索引主要使用 B+Tree 数据结构:
1 | B+Tree 索引结构示意: |
为什么用 B+Tree 而不是 B-Tree 或 Hash?
| 数据结构 | 特点 | 适用场景 |
|---|---|---|
| B-Tree | 所有节点都存数据,查询效率不稳定 | 文件系统 |
| B+Tree | 只有叶子节点存数据,查询效率稳定,支持范围查询 | MySQL 默认 |
| Hash | 等值查询 O(1),不支持范围查询 | Memory 引擎 |
| R-Tree | 空间数据索引 | 地理信息数据 |
四、索引类型
4.1 按数据结构分类
| 索引类型 | 说明 | 适用场景 |
|---|---|---|
| B+Tree 索引 | 默认索引类型,支持全值匹配、范围查询 | 绝大多数场景 |
| Hash 索引 | 等值查询极快,不支持范围查询 | Memory 引擎 |
| Full-Text 索引 | 全文检索 | 大文本字段搜索 |
| R-Tree 索引 | 空间索引 | 地理坐标数据 |
4.2 按功能分类
| 索引类型 | 关键字 | 特点 | 数量限制 |
|---|---|---|---|
| 主键索引 | PRIMARY KEY | 唯一标识每行记录,非空且唯一 | 1个 |
| 唯一索引 | UNIQUE | 索引列值必须唯一,允许NULL | 多个 |
| 普通索引 | INDEX | 无限制,仅加速查询 | 多个 |
| 组合索引 | INDEX(a,b,c) | 多列联合索引,遵循最左前缀 | 多个 |
| 全文索引 | FULLTEXT | 针对文本内容的关键词检索 | 多个 |
4.3 按存储方式分类
| 索引类型 | 说明 | 存储 |
|---|---|---|
| 聚簇索引 | 数据与索引存储在一起 | 叶子节点存数据行 |
| 非聚簇索引 | 数据与索引分开存储 | 叶子节点存主键值 |
InnoDB 的聚簇索引:
- 主键索引就是聚簇索引,叶子节点存储完整数据行
- 二级索引(非主键索引)的叶子节点存储主键值
- 查询二级索引时,可能需要回表查询(根据主键值再去聚簇索引查数据)
1 | -- 聚簇索引示意 |
五、索引的创建与管理
5.1 创建索引
1 | -- 方式1:CREATE INDEX |
5.2 查看与删除索引
1 | -- 查看表的所有索引 |
5.3 索引创建示例
1 | -- 创建员工表并建立合理索引 |
六、索引设计原则
6.1 核心设计原则
| 设计原则 | 场景案例 | 实现方法 | 注意事项 |
|---|---|---|---|
| 选择区分度高的列 | 用户表手机号唯一性达95%+ | ALTER TABLE users ADD UNIQUE INDEX idx_mobile(mobile) |
避免在低区分度字段(如性别)建索引 |
| 联合索引遵循最左前缀 | 高频查询 WHERE age>20 AND city='北京' |
ALTER TABLE users ADD INDEX idx_age_city(age, city) |
若单独查询city需调整索引顺序 |
| 避免索引列参与计算 | 按年份过滤订单 | 优化为 WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31' |
使用函数或计算会导致索引失效 |
| 覆盖索引减少回表 | 查询用户姓名 | 建立覆盖索引 (id, name) |
避免 SELECT * |
| 外键字段必建索引 | 订单表关联用户 | ALTER TABLE orders ADD INDEX idx_user_id(user_id) |
防止关联查询时全表扫描 |
| 前缀索引优化长文本 | 文章内容字段 | ALTER TABLE articles ADD INDEX idx_content(content(20)) |
需平衡索引长度与查询效率 |
| 控制单表索引数量 | 用户表已有5个索引 | 通过慢查询日志分析必要性,删除冗余索引 | 索引过多影响写性能,建议不超过5个 |
| 业务逻辑驱动设计 | 商品按价格排序 | ALTER TABLE products ADD INDEX idx_price(price) |
排序+过滤时优先满足过滤字段 |
6.2 最左前缀法则
如果索引了多列(联合索引),查询必须从索引的最左列开始,并且不跳过索引中的列。
1 | -- 创建联合索引 (a, b, c) |
最左前缀法则图解:
1 | 联合索引 idx_abc(a, b, c) 的 B+Tree 结构: |
6.3 索引失效的常见场景
| 索引失效场景 | 示例 | 原理分析 |
|---|---|---|
| 对索引列进行运算/函数操作 | WHERE YEAR(create_time) = 2023 |
运算破坏索引的有序性 |
| 隐式类型转换 | WHERE varchar_col = 100 |
MySQL 需类型转换,破坏索引匹配 |
| LIKE 左模糊匹配 | WHERE name LIKE '%张三' |
B+树按最左字符排序,前导通配符无法利用索引 |
| OR 连接非索引条件 | WHERE id=1 OR age>20(age无索引) |
优化器判定全表扫描成本更低 |
| 违反最左前缀原则 | 索引(a,b,c),查询 WHERE b=2 |
缺失最左列无法缩小扫描范围 |
| 使用否定条件 | WHERE status != 'active' |
NOT、<> 需要扫描大部分数据 |
| 数据分布倾斜 | 索引列95%的值相同 | 区分度过低,优化器选择全表扫描 |
| JOIN 字段类型不匹配 | ON t1.int_col = t2.varchar_col |
类型转换导致无法使用索引 |
1 | -- ❌ 索引失效示例 |
七、阿里巴巴索引规约
7.1 强制规约
【强制】 业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。
说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。
【强制】 超过三个表禁止 JOIN。需要 JOIN 的字段,数据类型必须绝对一致;多表关联查询时,保证被关联的字段需要有索引。
说明:即使双表 JOIN 也要注意表索引、SQL 性能。
【强制】 在 VARCHAR 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度即可。
说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分度会高达 90%以上,可以使用
count(distinct left(列名, 索引长度))/count(*)的区分度来确定。【强制】 页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。
说明:索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。
7.2 推荐规约
【推荐】 如果有 ORDER BY 的场景,请注意利用索引的有序性。ORDER BY 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况。
- 正例:
WHERE a=? AND b=? ORDER BY c;索引:a_b_c - 反例:
WHERE a>10 ORDER BY b;索引a_b无法排序。
- 正例:
【推荐】 利用覆盖索引来进行查询操作,避免回表。
说明:能够建立索引的种类分为主键索引、唯一索引、普通索引三种,而覆盖索引只是一种查询效果,用 explain 的结果,extra 列会出现:
using index。【推荐】 利用延迟关联或者子查询优化超多分页场景。
- 正例:先快速定位需要获取的 id 段,然后再关联:
1
2
3SELECT a.* FROM 表1 a,
(SELECT id FROM 表1 WHERE 条件 LIMIT 100000, 20) b
WHERE a.id = b.id;【推荐】 SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts 最好。
consts:单表中最多只有一个匹配行(主键或者唯一索引)ref:使用普通索引range:对索引进行范围检索index:索引物理文件全扫描,速度非常慢
【推荐】 建组合索引的时候,区分度最高的在最左边。
- 正例:如果
WHERE a=? AND b=?,a 列的几乎接近于唯一值,那么只需要单建idx_a索引即可。 - 说明:存在非等号和等号混合时,把等号条件的列前置。如
WHERE c>? AND d=?,即使 c 的区分度更高,也必须把 d 放在索引的最前列,即索引idx_d_c。
- 正例:如果
【推荐】 防止因字段类型不同造成的隐式转换,导致索引失效。
7.3 参考规约
- 【参考】 创建索引时避免有如下极端误解:
- 宁滥勿缺:认为一个查询就需要建一个索引
- 宁缺勿滥:认为索引会消耗空间、严重拖慢记录的更新以及行的新增速度
- 抵制唯一索引:认为业务的唯一性一律需要在应用层通过”先查后插”方式解决
八、索引优化实战
8.1 使用 EXPLAIN 分析查询
1 | -- 查看查询执行计划 |
type 访问类型(性能从好到差):
| type | 说明 | 示例 |
|---|---|---|
| system | 表只有一行 | 系统表 |
| const | 主键或唯一索引等值查询 | WHERE id = 1 |
| eq_ref | JOIN 时主键或唯一索引关联 | t1 JOIN t2 ON t1.id = t2.id |
| ref | 普通索引等值查询 | WHERE name = '张三' |
| range | 索引范围查询 | WHERE id BETWEEN 1 AND 100 |
| index | 索引全扫描 | SELECT id FROM table |
| ALL | 全表扫描 | 无索引或索引失效 |
8.2 索引优化案例
1 | -- 案例1:覆盖索引优化 |
💡 小结:本章详细介绍了 MySQL 的存储引擎(InnoDB/MyISAM 对比)和索引优化知识。存储引擎选择 InnoDB 作为默认引擎;索引设计需遵循最左前缀法则、选择区分度高的列、避免索引失效场景;同时参考阿里巴巴索引规约,合理使用 EXPLAIN 分析并优化查询性能。

