一、为什么需要索引
数据库最核心的问题之一,就是:
如何在海量数据中快速找到目标数据。
假设现在有一张员工表:
select * from EMP where empno = 998877;
如果表中有 800 万条数据,并且没有索引,那么 MySQL 只能:
- 从第一条开始
- 一条一条比较
- 直到找到目标数据
这种方式叫:
全表扫描(Full Table Scan)
数据量小时影响不大。
但数据量一大:
- 查询变慢
- CPU 占用高
- 磁盘 IO 爆炸
- 并发高时数据库可能直接被拖死
因此:
索引的本质
索引的核心目的:
减少磁盘 IO 次数,提高查询效率
索引是典型的:
空间换时间
二、索引的代价
索引并不是免费的
索引会带来:
- 查询变快
- 插入变慢
- 更新变慢
- 删除变慢
因为:
每次修改数据时:
- 不仅要修改数据
- 还要维护索引结构
所以:
索引适合读多写少场景
三、磁盘基础知识
理解索引之前,必须先理解磁盘
因为:
MySQL 数据最终存储在磁盘中
1. 磁盘结构
机械硬盘核心结构:
- 盘片(Platter)
- 磁道(Track)
- 扇区(Sector)
- 磁头(Head)
2. 扇区
磁盘最小存储单位:
扇区(Sector)
传统大小:
512 字节
现在部分磁盘:
4096 字节
3. CHS 定位方式
磁盘定位数据时:
通过:
- 柱面(Cylinder)
- 磁头(Head)
- 扇区(Sector)
定位数据。
简称:
CHS
现代系统通常使用:
LBA 线性地址
但底层最终还是会转换成 CHS。
四、磁盘 IO 的问题
机械硬盘最大的问题:
寻道
磁头移动非常慢。
因此:
随机 IO 很昂贵
1. 随机访问
本次访问位置
和上次访问位置差距很大
磁头需要大量移动
效率低
2. 顺序访问
连续读取相邻数据
磁头移动很少。
效率高。
五、操作系统的 IO 单位
磁盘最小单位是:
512B
但操作系统不会每次只读 512B
因为太小了。
系统通常按:
块(Block)
读取数据。
Linux 常见:
4KB
六、MySQL 的 IO 单位
InnoDB 并不是按 4KB 读取。
而是:
16KB
查看:
SHOW GLOBAL STATUS LIKE 'innodb_page_size';
结果:
16384
即:
16 * 1024 = 16KB
这个单位叫:
Page(页)
七、为什么 MySQL 要按 Page 读取
假设有如下数据:
1
2
3
4
5
如果每次只读取一条:
查找 id=5:
需要:
5 次 IO
但如果:
整个 Page 一次性加载:
只需 1 次 IO
后续数据都在内存中
局部性原理
程序访问数据时:
通常会:
- 访问附近数据
- 重复访问热点数据
因此:
一次读取一页非常划算
八、Buffer Pool
MySQL 会在内存中维护:
Buffer Pool(缓冲池)
作用:
- 缓存热点页
- 减少磁盘 IO
- 提高查询速度
因此:
MySQL 实际工作流程:
磁盘 -> Page -> Buffer Pool -> CPU
九、为什么数据默认按主键排序
建表:
create table user(
id int primary key,
age int,
name varchar(16)
);
即使乱序插入:
insert into user values(3,18,'杨过');
insert into user values(1,56,'欧阳锋');
insert into user values(5,36,'郭靖');
查询时:
select * from user;
结果依然有序
原因:
InnoDB 按主键组织数据
十、Page 内部结构
一个 Page 内部:
数据并不是数组。
而是:
链表结构
因为:
链表:
- 插入快
- 删除快
但:
- 查询慢
所以:
MySQL 又引入:
页目录(Page Directory)
十一、页目录
类似于:
书的目录
没有目录:
只能一页一页翻。
有目录:
直接定位。
因此:
MySQL 在 Page 内:
维护目录。
查询时:
先查目录。
再定位数据。
十二、多 Page 问题
数据量继续增大。
一个 Page 放不下。
于是:
会有多个 Page。
多个 Page:
使用双向链表连接。
问题来了:
Page 之间仍然需要遍历
还是慢。
十三、Page 目录
于是:
MySQL 再次优化。
给Page 建目录
目录项存储:
键值 + Page 指针
通过目录:
快速定位目标 Page。
十四、B+ 树诞生
继续对目录分页。
再给目录建立目录。
最终形成:
B+ 树
这就是:
InnoDB 索引底层结构
十五、B+ 树为什么快
核心原因:
大幅减少磁盘 IO
查找流程:
根节点
↓
中间节点
↓
叶子节点
树高度非常低。
即使千万数据:
通常只需:
3~4 次 IO
十六、为什么不用其他数据结构
1. 链表
问题:
只能线性查找
太慢。
2. 二叉搜索树
问题:
可能退化:
链表
3. AVL / 红黑树
虽然平衡。
但:
树太高
因为:
每个节点只有两个孩子。
IO 次数仍然较多。
4. Hash
优点:
O(1)
缺点:
不支持范围查询
例如:
where id > 100
Hash 无法高效处理。
5. B 树
B 树节点:
- 既存数据
- 又存指针
导致:
一个节点能存的 key 变少
树变高。
十七、B+ 树 VS B 树
B 树
特点:
- 非叶子节点也存数据
- 节点能存储的数据更少
- 树更高
B+ 树
特点:
- 非叶子节点只存 key
- 一个节点能存更多 key
- 树更矮
- IO 更少
并且:
叶子节点天然有序
适合:
范围查询
例如:
between
>
<
order by
十八、聚簇索引与非聚簇索引
1. 聚簇索引(InnoDB)
特点:
数据和索引放在一起
叶子节点:
直接存储完整数据。
因此:
主键索引查询非常快。
2. 非聚簇索引(MyISAM)
特点:
索引和数据分离
叶子节点:
存储:
数据地址
需要:
再去数据文件找数据。
十九、回表查询
InnoDB 普通索引:
叶子节点不存完整数据。
只存:
主键值
查询流程:
普通索引
↓
拿到主键
↓
再查主键索引
这个过程:
回表查询
二十、主键索引
创建方式:
1. 字段后直接添加
create table user(
id int primary key,
name varchar(30)
);
2. 表末尾添加
create table user(
id int,
name varchar(30),
primary key(id)
);
3. 后续添加
alter table user add primary key(id);
主键索引特点
- 一个表只能有一个主键
- 不能重复
- 不能为 NULL
- 查询效率高
二十一、唯一索引
创建:
create table user(
id int primary key,
name varchar(30) unique
);
或者:
alter table user add unique(name);
特点
- 可有多个
- 不允许重复
- 可提高查询效率
二十二、普通索引
创建:
create index idx_name on user(name);
或者:
alter table user add index(name);
特点
- 允许重复
- 实际开发最常见
- 提高查询效率
二十三、全文索引
适用于:
大文本搜索
例如:
- 文章
- 博客
- 新闻
创建:
FULLTEXT(title, body)
使用全文索引
错误方式:
where body like '%database%'
不会走索引
正确方式:
SELECT * FROM articles
WHERE MATCH(title, body)
AGAINST('database');
explain 分析索引
查看 SQL 是否走索引:
explain select * from articles;
关键字段:
key
如果:
key = NULL
说明:
没有使用索引
二十四、查看索引
1.
show keys from 表名;
2.
show index from 表名;
3.
desc 表名;
二十五、删除索引
删除主键索引
alter table 表名 drop primary key;
删除普通索引
alter table 表名 drop index 索引名;
或者:
drop index 索引名 on 表名;
二十六、索引创建原则
适合创建索引
1. 经常查询的字段
例如:
where
order by
group by
涉及字段。
2. 区分度高字段
例如:
身份证
手机号
学号
不适合创建索引
1. 区分度低字段
例如:
性别
是否删除
2. 更新频繁字段
因为维护索引代价大。
3. 很少查询字段
纯浪费空间。
二十七、索引核心总结
索引本质
减少磁盘 IO 次数
InnoDB 核心结构
Page + B+树
为什么快
- 树高度低
- Page 读取
- Buffer Pool 缓存
- 顺序 IO
- 减少随机 IO
InnoDB 默认索引
主键索引
普通索引的问题
可能回表
最终核心
数据库性能优化:
本质就是:
减少磁盘 IO
因为:
CPU 远比磁盘快
这就是有关MySQL索引相关的问题啦,这是MySQL学习的两大难关之一,下面我们将跨过下一个难关 --- MySQL的事务,敬请期待啦~~