MySQL的索引

一、为什么需要索引

数据库最核心的问题之一,就是:

如何在海量数据中快速找到目标数据。

假设现在有一张员工表:

复制代码
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的事务,敬请期待啦~~

相关推荐
金色光环2 小时前
【DSP学习】DSP28335 点亮LED
嵌入式硬件·学习·dsp开发
qq_414256572 小时前
JavaScript中类继承中super关键字的调用执行逻辑
jvm·数据库·python
我是发哥哈2 小时前
跨AI模型生成视频的五大维度对比:选型避坑指南
大数据·人工智能·学习·机器学习·chatgpt·音视频
代码丰2 小时前
RAG 文档切分、索引优化与 Reranker 学习笔记
数据库
Elastic 中国社区官方博客2 小时前
Elastic 9.4:Workflows 正式发布、Agent Builder 更新,以及 Prometheus / PromQL 支持
运维·数据库·人工智能·elasticsearch·搜索引擎·信息可视化·prometheus
ㄟ留恋さ寂寞2 小时前
html如何修改备注
jvm·数据库·python
2401_884454152 小时前
c++如何读取YAML格式配置文件_yaml-cpp库快速入门【详解】
jvm·数据库·python
2301_775639893 小时前
mysql升级时如何使用Ansible进行自动化部署_mysql自动化管理
jvm·数据库·python
WUYOUGYLU3 小时前
第一次买云服务器,最该先看什么?
数据库