MySQL⑥存储引擎、索引
1、MySQL 体系结构
体系结构图
-
系统管理和控制工具:备份与恢复、安全、复制、集群等
-
连接层:引入线程池的概念
- 认证,线程管理,连接管理等
- 可以实现基于 SSL 的安全连接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
-
服务层:完成大多数的核心服务功能
- SQL 接口:处理 SQL 命令,存储过程,试图,触发器等
- 解析器
- 查询优化器:是否使用索引等
- 缓存
-
引擎层:内存、索引、存储管理
- 真正负责 MySQL 中数据的存储和提取,服务器通过 API 和存储引擎进行通信。
- 不同的存储引擎具有不同的功能,根据需要选取合适的存储引擎。
- 实现索引
-
存储层
2、存储引擎
存储引擎:存储数据、建立索引、更新/查询数据等技术的实现方式。
- 基于表,而不是基于库(因此存储引擎也称为表类型)。
- 建表的时候可以指定存储引擎,否则自动使用默认引擎。
2.1、特点
2.1.1、InnoDB
InnoDB:兼顾高可靠性和高性能的通用存储引擎。
- MySQL 5.5 之后的默认存储引擎。
- 详细讲解:
特点
- 事务:DML 操作遵循 ACID 模型。
- 外键:支持 FOREIGN KEY 约束,保证数据的完整性和正确性。
- 行级锁:提高并发访问性能。
文件
- InnoDB 引擎的每张表,都对应一个 ibd 表空间文件。
- 存储表结构信息、数据和索引。
逻辑存储结构
2.1.2、MyISAM
MyISAM:MySQL 早期的默认存储引擎。
特点
- 不支持事务、外键。
- 支持表锁,不支持行级锁。
- 访问速度快。
文件
- sdi:存储表结构信息
- MYD:存储数据
- MYI:存储索引
2.1.3、Memory
Memory:表数据存储在内存中。
由于受到硬件、断电等问题的影响,只能作临时表或缓存使用。
特点:存放再内存、支持 hash 索引。
文件:sdi 文件,存储表结构信息。
2.1.4、区别
| 特点 | InnoDB | MyISAM | Memory |
|---|---|---|---|
| 存储限制 | 64TB | 有 | 有 |
| 事务 | ? | - | - |
| 外键 | ? | - | - |
| 锁机制 | 行级锁 | 表锁 | 表锁 |
| B+tree 索引 | ? | ? | ? |
| Hash 索引 | - | - | ? |
| 全文索引 | ?(5.6 后) | ? | - |
| 空间使用 | 高 | 低 | N/A |
| 内存使用 | 高 | 低 | 中等 |
| 批量插入速度 | 低 | 高 | 高 |
面试常问:InnoDB 和 MyISAM 的区别
主要从 事务、外键、锁 的角度,也可以从索引结构、存储限制等方面深入回答。
2.2、选择
原则:只有合不合适,没有好坏之分。
- 根据应用系统的特点,选择合适的存储引擎。
- 对于复杂的应用系统,可以选择多种存储引擎组合。
- InnoDB
- 对事务、并发性要求高
- 除了读操作和插入操作,还有很多更新、删除操作。
- MyISAM(很少使用,用 MongoDB 替代)
- 对事务、并发性要求不高。
- 以读操作和插入操作为主,很少更新、删除操作。
- Memory(很少使用,用 Redis 替代)
- 通常用于临时表和缓存,访问速度快
- 缺点是对表的大小有限制,无法保障数据的安全性。
3、索引(!)
索引(index):帮助 MySQL 高效获取数据的有序数据结构。
数据库系统既维护数据,还维护着实现了特定查找算法的数据结构(即索引,以某种方式引用数据)
优点
- 提高数据查询效率,降低数据库 IO 成本。
- 通过有序索引列对数据排序,降低排序成本,降低 CPU 消耗。
缺点:索引列也占用空间,导致表的更新速度降低。
3.1、数据结构
MySQL 索引在存储引擎层实现
- 不同存储引擎的索引结构可能有所不同。
- 在平时使用中,若没有特别指明,则是 B+ 树索引。
主要的索引结构
| InnoDB | MyISAM | Memory | |
|---|---|---|---|
| B+tree | ? | ? | ? |
| Hash | - | - | ? |
| R-tree | - | ? | - |
| Full-text | 5.6 之后? | ? | - |
3.1.1、说明
InnoDB 使用优化的 B+ 树索引结构。
(先了解二叉树、B 树、标准的 B+ 树)
二叉树
-
结构:每个结点存放:key(包括数据)、指针
-
特点
- 线性情况:假如元素是顺序插入,会形成单向链表,而不是二叉树。
- 大数据量:层级多,检索速度慢。
-
图例
-
红黑树可以解决线性情况的问题,但是无法解决大数据量的问题。
B 树(多路平衡查找树)
-
结构
- 每个结点存放:key(包括数据)、指针
- 相对于二叉树,每个结点可以存储多个数据,有多个分支
-
图例:最大度数为 5 的 B 树
-
特点
- 每个结点最多存储 4 个 Key、5 个指针。
- 当结点存储的 Key 数量达到度数,中间元素向上分裂。
- 每个结点都会存放指针和数据。
B+ 树(B 树的变体)
-
结构:
- 非叶子结点仅存放 Key(不包括数据) 和指针;叶子结点存储 Key(包括数据)。
- 叶子结点形成一个单向链表。
-
即:非叶子结点仅起到索引作用,具体数据存储在叶子结点中。
-
图例:最大度数为 4 的 B+ 树
3.1.2、B+tree(MySQL)
MySQL 索引的数据结构对 B+ 树进行了优化
相比标准的 B+ 树,叶子节点形成双向循环链表。
3.1.3、Hash
相当于数组 + 链表,参考
- Memory:支持
- InnoDB:不支持,但提供自适应哈希索引的机制。
通过 Hash 算法,将 key 值转换为 Hash 值,映射到 Hash 表的对应位置。
-
哈希冲突:通过拉链法(形成链表)的方式解决。
-
特点
- 支持等值查询(=,in),不支持范围查询(between,> 等)
- 不支持排序
- 查询效率高,通常只需一次检索(前提是不存在哈希冲突)
-
图示
面试题:为什么 InnoDB 存储引擎选择使用 B+tree 索引结构
(思路:对比二叉树、B 树、Hash)
- 二叉树可能出现线性情况;在数据量大的情况下,层级多,性能低。
- B 树的每个节点都会保存数据,导致一页中能存储的 Key 和指针数目减少、在数据量大的情况下,需要增加树的高度,导致性能降低。
- Hash 索引不支持范围查询和排序。
3.2、分类
3.2.1、MySQL 索引类型
MySQL 中索引的具体类型如下
| 含义 | 特点 | 关键字 | |
|---|---|---|---|
| 主键索引 | 针对于表中主键创建的索引 | 默认自动创建,只有一个 | PRIMARY |
| 唯一索引 | 避免同一个表中某数据列中的值重复 | 可以有多个 | UNIQUE |
| 常规索引 | 快速定位特定数据 | 可以有多个 | |
| 全文索引 | 查找文本关键词,而不是比较索引值 | 可以有多个 | FULLTEXT |
3.2.2、InnoDB 索引存储形式
在 InnoDB 存储引擎中,根据索引的存储形式分为以下类型
| 含义 | 特点 | |
|---|---|---|
| 聚集索引 (Clustered) |
叶节点保存行数据 (数据与索引一起存储) |
有且只有一个 |
| 二级索引 (Secondary) |
叶节点关联主键 (数据与索引分开存储) |
0 或多个 |
聚集索引的选取规则
- 存在主键:使用主键索引。
- 不存在主键:使用首个唯一索引。
- 不存在主键和唯一索引:自动生成一个 rowid 作为隐藏的聚集索引。
示例:索引查找过程
以
SELECT * FROM user WHERE name = 'Arm'为例。
由于不是查询条件不是主键。因此先查二级索引,再查聚类索引。
-
先根据 name 字段的二级索引,进行匹配查询,查找到对应的主键的值。
-
回表查询:根据二级索引查到的主键,到聚类索引中查找对应的行数据。
3.4、语法
索引相关语法
- 命名:一般为
idx_表名_列名- 联合索引:为多个列创建的索引。
-
创建索引
- UNIQUE:唯一索引
- FULLTEXT:全文索引
- 不指定:常规索引
CREATE [UNIQUE |FULLTEXT] INDEX 索引名 ON 表名 (列名, ... ) ; -
查看索引
SHOW INDEX FROM 表名; -
删除索引
DROP INDEX 索引名 ON 表名;
实例:根据不同情况,为
t_user表创建索引
-
name 姓名字段,可能重复。
# 常规索引 CREATE INDEX idx_user_name ON t_user(name); -
phone 手机号字段,非空且唯一。
# 唯一索引 CREATE UNIQUE INDEX idx_user_phone ON t_user(phone); -
为 profession、age、status 字段创建索引。
# 联合索引 CREATE INDEX idx_user_pro_age_sta ON t_user(profession, age, status);
3.5、SQL 性能分析
3.5.1、执行频率
查询 SQL 语句的执行频率
- 以得知数据库以查询为主,还是以增删改为主。
- 若以查询为主,则可考虑设计索引。
通过执行频率可以得知,当前数据库的 SQL 整体执行情况。
语法:show [session|global] status
3.5.2、慢查询日志
记录执行时间超过指定参数的 SQL 语句
slow_query_log:慢查询日志开关long_query_time:指定时间通过慢查询日志,可以定位执行效率低的 SQL 并优化。
慢查询日志
# 查看是否开启
SHOW VARIABLES LIKE 'slow_query_log;
MySQL 配置文件(/etc/my.cnf)
# 开启MySQL慢查询日志
slow_query_log=1
# 设置慢日志的时间为2秒,SQL语句执行时间超过2秒就会视为慢查询
long_query_time=2
重启 MySQL 服务器,完成配置。
3.5.3、profile
查看 SQL 语句的执行耗时
查看 MySQL 是否支持 profile 操作
SELECT @@have_profiling;
开启 profiling
SET profiling = 1;
3.5.4、explain
查看 SELECT 语句的执行信息
- 在 DQL 语句之前使用
EXPLAIN关键字- 也可以使用
DESC关键字
执行信息中,各个字段的含义如下
| 含义 | 说明 | |
|---|---|---|
| id | 序列号,即 DQL 的执行顺序 | id 大的先执行;相同 id 从上到下执行 |
| select_type | 查询类型 | SIMPLE:简单表,无需连接或者子查询 PRIMARY:主查询,即外层的查询 UNION:连接查询 SUBQUERY:子查询 |
| type | 连接类型 | 性能由好到差:NULL、system、const、eq_ref、ref、range、 index、all 。 |
| possible_key | 可能使用的索引 | 一个或多个,NULL 表示没有 |
| key | 实际使用的索引 | |
| key_len | 索引中使用的字节数 | 值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下, 长度越短越好 |
| rows | 需执行查询的行数 | innodb 引擎的表中,是一个估计值,可能并不准确 |
| filtered | 返回结果的行数占需读取行数的百分比 | 值越大越好 |
| Extra | 其他信息 |
3.6、使用
- 单列索引:一个索引只包含单个列
- 联合索引:一个索引包含多个列
3.6.1、最左前缀法则
最左前缀法则:条件查询的字段,包括从索引的最左列开始,不跳过索引的任一列。
- 使用联合索引必须遵守最左前缀法则。
- 如果跳过某一列,会导致该列之后的索引失效。
示例
# 创建一个联合索引
CREATE INDEX idx_prof_age_status
ON tb_user(profession, age, status);
不失效情况
EXPLAIN SELECT * FROM tb_user
WHERE profession = '计算机科学' AND age = 17 AND status = '0';
EXPLAIN SELECT * FROM tb_user
WHERE profession = '计算机科学' AND age = 17;
EXPLAIN SELECT * FROM tb_user
WHERE profession = '计算机科学';
失效情况
# 完全失效:跳过profession,后面两列索引失效
EXPLAIN SELECT * FROM tb_user
WHERE age = 17 AND status = '0';
# 完全失效:跳过profession和age,后面索引失效
EXPLAIN SELECT * FROM tb_user
WHERE status = '0';
# 部分失效:跳过age,age之后的索引失效
EXPLAIN SELECT * FROM tb_user
WHERE profession = '计算机科学' AND status = '0';
注意:只要最左列在查询条件中存在,与条件编写的先后顺序无关。
# 调换profession和age顺序,仍满足最左前缀法则
EXPLAIN SELECT * FROM tb_user
WHERE age = 17 AND profession = '计算机科学';
3.6.2、覆盖索引
覆盖索引:查询使用了索引,且索引中包含(覆盖)了所有要查询的列。
- 覆盖索引一般是联合索引
- 应尽量使用覆盖索引,且减少使用
SELECT *
示例:SELECT id, name FROM tb_user WHERE name='Arm'
-
回表查询:根据二级索引查到的主键,到聚类索引中查找对应的行数据。
-
覆盖查询:根据二级索引查到主键 id,和 name 都已查出(该二级索引就是一个覆盖索引)
3.6.3、前缀索引
前缀索引:为字符串类型的字段建立的索引。
- 如果不建立索引,字符串(varchar, text 等)的索引占用内存大
- 浪费大量磁盘 IO,影响查询效率
- 根据字符串的一部分前缀,设计索引。
语法:
CREATE INDEX 索引名
ON 表名(COLUMN(前缀长度))
前缀长度:根据索引的选择性来决定
- 选择性:数据表中,不重复的索引值与记录总数的比值。
- 索引查询效率与选择性成正比。
- 唯一索引:选择性为 1,查询效率最高。
示例:
3.6.4、SQL 提示
SQL 提示:在 SQL 语句中加入提示信息,提示 MySQL 做出相应动作。
USE INDEX:建议使用指定索引。FORCE IDNEX:强制使用指定索引。IGNORE INDEX:忽略指定索引。
示例
# 建议使用指定索引
EXPLAIN SELECT * FROM tb_user
USE INDEX(idx_user_pro)
WHERE profession = '软件工程';
# 强制使用指定索引
EXPLAIN SELECT * FROM tb_user
FORCE INDEX(idx_user_pro)
WHERE profession = '软件工程';
# 忽略指定索引
EXPLAIN SELECT * FROM tb_user
IGNORE INDEX(idx_user_pro)
WHERE profession = '软件工程';
3.7、失效情况
-
索引本身失效。
-
最左前缀法则:联合索引不遵守时,部分索引失效。
-
范围查询
-
联合索引中某个列使用范围查询(
>和<),会导致联合索引中该列右侧的列索引失效。 -
解决方案:在业务允许的情况下,改用
>=和<= -
示例:age 右侧的 status 列索引失效。
# 联合索引 CREATE INDEX idx_prof_age_status ON tb_user(profession, age, status); # 范围查询:age SELECT * FROM tb_user WHERE profession = '计算机科学' AND age < 20 AND status='0';
-
-
索引列运算:在索引列上进行运算。
# 索引 CREATE INDEX idx_phone ON tb_user(phone); # 导致idx_phone失效 SELECT * FROM tb_user WHERE SUBSTRING(phone,8,4) = '6333'; -
字符串不加引号:数据库会隐式类型转换,将数字转换为字符串,但索引失效。
# 正常 SELECT * FROM tb_user WHERE phone = '15882288333'; # 索引失效 SELECT * FROM tb_user WHERE phone = 15882288333; -
头部模糊查询
# 正常:尾部、中间模糊匹配 SELECT * FROM tb_user WHERE profession = '计算%'; SELECT * FROM tb_user WHERE profession = '计%程'; # 失效:头部模糊匹配 SELECT * FROM tb_user WHERE profession = '%工程'; SELECT * FROM tb_user WHERE profession = '%工%'; -
or 连接条件:or 连接左右两侧的列都有索引才有效
-
数据分布影响:当查询记录数是表的大部分数据,MySQL 评估使用全表查询效率更高,索引失效。
3.8、设计原则
- 建立索引
- 数据量较大,且查询比较频繁的表
- 常作为查询条件(where)、排序(order by)、分组(group by)操作的字段
- 尽量选择区分度高的列建立索引,尽量建立唯一索引。
- 前缀索引:字符串类型的字段,且字段长度较长。
- 联合索引:尽量减少单列索引。联合索引往往可以覆盖索引,节省存储空间,避免回表,提高查询效率。
- 索引数量:要控制索引的数量。索引越多,维护开销越大,影响增删改的效率。
- NULL 值:如果索引列不能存储NULL值,在创建表时使用 NOT NULL约束。