文章目录
- 
- 索引的概念
 - 磁盘与索引的联系
 - 
- [1. 磁盘与MySQL的关系](#1. 磁盘与MySQL的关系)
 - [2. 磁盘的核心结构与数据存储](#2. 磁盘的核心结构与数据存储)
 - [3. 如何定位扇区](#3. 如何定位扇区)
 - [4. 系统与磁盘的高效交互单位](#4. 系统与磁盘的高效交互单位)
 - [5. 磁盘随机访问与连续访问](#5. 磁盘随机访问与连续访问)
 - [6. MySQL 与磁盘交互基本单位](#6. MySQL 与磁盘交互基本单位)
 
 - 建立共识
 - 索引的理解
 - 
- 理解单个Page
 - 理解多个Page
 - 其他数据结构为何不行?
 - 为何不用B树作为底层索引?
 - 
- [1. B+树非叶子节点不存数据,能减少IO次数](#1. B+树非叶子节点不存数据,能减少IO次数)
 - [2. B+树叶子节点有序相连,适合范围查询](#2. B+树叶子节点有序相连,适合范围查询)
 
 - [聚簇索引 VS 非聚簇索引](#聚簇索引 VS 非聚簇索引)
 
 - 索引操作
 
 
索引的概念
可以把索引理解成数据库里的"目录",就像你看书找某章、查字典找某个字时,不会从头翻到尾,而是先看目录/检字表一样,数据库查数据时,也会先看索引,快速定位到数据存在的位置,不用"逐行翻找"。
举个具体的例子:
比如你有一本1000页的《数据库使用手册》,想找"查询优化"的内容。
- 没目录:你得从第1页开始,一页一页翻,直到找到相关内容,可能要翻几百页,很慢;
 - 有目录:你直接看目录,发现"查询优化"在第350页,直接翻到350页就能找到,快很多。
 
数据库里的"数据"就像手册里的内容,"索引"就是这个"目录",它不存具体数据,只存"某条数据在磁盘的哪个位置"(比如"员工编号998877的记录在磁盘第123个数据块里"),帮数据库跳过"逐行扫描"的慢步骤,直接定位到目标数据。
由于数据库建立索引是需要空间的!索引是"用空间换时间";而且如果手册内容改了(比如加了新章节),目录也要跟着改,数据库里改数据(插入/更新/删除)时,索引也得同步更新,会多花一点时间。但换回来的是更加高效的查询效率。
常见索引分为:
- 主键索引(primary key)
 - 唯一索引(unique)
 - 普通索引(index)
 - 全文索引(fulltext)
 
试想一下,如果你在一张拥有百万数据的表中没有使用索引去查询,那么数据库只能从头到尾逐条扫描整个表的所有百万条记录,逐个比对你要查找的值,这个时间复杂度不用多说,放在哪里都太慢了。
磁盘与索引的联系
1. 磁盘与MySQL的关系
MySQL的所有数据最终存在磁盘(机械外设,效率远低于电子元件),所以"提升磁盘IO效率"是MySQL性能优化的关键------毕竟数据读写都要依赖磁盘操作。
2. 磁盘的核心结构与数据存储
- 磁盘物理组成:包含串行接口、主轴(带轴承和马达,驱动盘片转)、磁头(读写数据,1个盘面配1个)、磁头臂/音圈马达(带动磁头移动)等。
 - 盘片数据划分 :
- 盘面表面是"同心圆",每个圆叫磁道(最外圈是0磁道);
 - 每个磁道又分成多个扇区(默认512字节,正过渡到4K字节),扇区是磁盘最小物理存储单元;
 - 数据库文件本质是存在多个扇区里(文件大,需占用多扇区)。
 
 - 补充:Linux中多数文件(含MySQL文件)存在硬盘,仅proc、sys等是内存文件系统;例如
/var/lib/mysql目录,MySQL的表结构、数据、索引等文件都以磁盘文件形式存在。 

3. 如何定位扇区
靠"磁头(Heads)、柱面(Cylinder)、扇区(Sector)"三个参数定位,这种方式叫CHS寻址:
- 磁头:对应盘面(1个盘面1个磁头,定位"数据在哪个盘面");
 - 柱面:同半径的磁道叠成"圆柱"(定位"数据在哪个半径的磁道");
 - 扇区:磁道上的小分段(定位"数据在磁道的哪个具体位置")。
 
但实际系统软件用LBA(线性地址) ,把所有扇区编上连续序号,系统操作LBA地址,最后再转成CHS给磁盘硬件执行。
4. 系统与磁盘的高效交互单位
硬件层面能定位扇区,但系统及文件系统不直接按"512字节扇区"IO:
- 原因:扇区单位太小,读少量数据就要多次IO(效率低),且直接用扇区会让系统代码和硬件强绑定;
 - 解决:文件系统用4KB数据块作为IO基本单位------系统读磁盘时,一次读4KB(而非512字节),减少IO次数,提升效率。
 
5. 磁盘随机访问与连续访问
- 随机访问 :本次IO所给出的扇区地址和上次IO给出扇区地址
不连续,这样的话磁头在两次IO操作之间需要作比较大的移动动作才能重新开始读/写数据。 - 连续访问 :如果当次IO给出的扇区地址与上次IO结束的扇区地址是连续的,那磁头就能很快的开始这次IO操作,这样的多个IO操作称为
连续访问。 
6. MySQL 与磁盘交互基本单位
而MySQL 作为一款应用软件,可以想象成一种特殊的文件系统。它有着更高的IO场景,所以,为了提高基本的IO效率,MySQL 进行IO的基本单位是16KB(后面统一使用InnoDB 存储引擎讲解)。
            
            
              sql
              
              
            
          
          mysql> SHOW GLOBAL STATUS LIKE 'innodb_page_size';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| Innodb_page_size | 16384 |
+------------------+-------+
1 row in set (0.00 sec)
        也就是说,磁盘这个硬件设备的基本单位是512 字节,而MySQL InnoDB引擎使用16KB 进行IO交互。即MySQL 和磁盘进行数据交互的基本单位是16KB 。
建立共识
- MySQL 中的数据文件,是以
page为单位保存在磁盘当中的。 - MySQL 的
CURD操作,都需要通过计算,找到对应的插入位置,或者找到对应要修改或者查询的数据。 - 在特定时间内,数据一定是磁盘中有,内存中也有。后续操作完内存数据之后,以特定的刷新策略,刷新到磁盘。涉及到磁盘和内存的数据交互,也就是IO,基本单位就是
Page。 - MySQL 服务器在内存中运行的时候,在服务器内部申请了
Buffer Pool的大内存空间,来进行各种缓存。和磁盘数据进行IO交互。 
索引的理解
理解单个Page
MySQL 中要管理很多数据表文件,而要管理好这些文件,就需要先描述,再组织,我们目前可以简单理解成一个个独立文件是由一个或者多个Page构成的。
            
            
              c
              
              
            
          
          struct page
{
    struct page *next;
    string page *prev;
    char buffer[NUM];
}; --- 16KB , new page, 将所有的page用"链表"的形式管理起来 --- 在 buffer pool 内部,对mysql中的page进行了一个建模!
        因为有主键的问题,MySQL 会默认按照主键给我们的数据进行排序,插入数据时排序的目的,就是优化查询的效率。页内部存放数据的模块,实质上也是一个链表的结构,链表的特点也就是增删快,查询修改慢,所以优化查询的效率是必须的。

理解多个Page
通过上面的分析,页模式中在查询某条数据的时候直接将一整页的数据加载到内存中,以减少硬盘IO次数,从而提高性能 。现在的页模式内部,实际上是采用了链表的结构,前一条数据指向后一条数据,本质上还是通过数据的逐条比较来取出特定的数据。
如果有1千万条数据,需要多个Page来保存1千万条数据,多个Page彼此使用双链表链接起来,而且每个Page内部的数据也是基于链表的。那么,查找特定一条记录,也一定是线性查找。

页目录
在表中添加主键时,默认生成主键索引,为了更加高效的查找,page中存在页目录:即只保存部分表中数据,形成目录,查找目录可以一次性跳过多个不需要的数据,加快了单页查找效率。

单页情况
引入了目录后,如果要查找id=4记录,之前必须线性遍历4次,才能拿到结果 。现在直接通过目录2[3],直接进行定位新的起始位置,提高了效率 。通过键值 MySQL 会自动排序的另一目的是可以很方便引入目录。

多页情况
前面提到过,多个page之间通过链表遍历的方式非常浪费时间,为此为page增加一个索引 (原来的链表结构不变,方便线性范围查找),给Page也带上目录,使用一个目录项来指向某一页,而这个目录项存放的就是将要指向的页中存放的最小数据的键值(只存储目录不存储数据,所以可以管理非常多的叶子page ),和页内目录不同的地方在于,这种目录管理的级别是页,而页内目录管理的级别是行,其中,每个目录项的构成是:键值+指针 ,这样在查找时只需要根据目录去指定的page往下继续查找即可,不用加载所有的page也不用线性遍历,效率拉满(再说一遍:Page分为目录页和数据页,目录页只放各个下级Page的最小键值,查找的时候,自定向下找,只需要加载部分目录页到内存,即可完成算法的整个查找过程,大大减少了IO次数 ),这所有的前提都是以主键作为索引的,如果没有指定主键,InnoDB会采用隐藏主键。
引入页目录之后:(数据页)

引入目录页之后:

这其实就是B+树,是的你的感觉没错。
其他数据结构为何不行?
- 链表:线性遍历,效率低。
 - 二叉搜索树:退化问题,可能退化成为线性结构,效率大幅降低。
 - AVL &&红黑树:虽然是平衡或者近似平衡,但是毕竟是二叉结构,相比较多阶B+树,意味着树整体过高,IO效率更低,而且范围查找的效率也不如B+树。
 - Hash :官方的索引实现方式中,MySQL 是支持HASH的,不过
InnoDB 和 MyISAM并不支持。Hash根据其算法特征,决定了虽然有时候也很快(O(1)),不过,在面对范围查找时就明显不行。 
常见的mysql存储引擎采用的数据结构
| Storage Engine | Permissible Index Types | 
|---|---|
| InnoDB | BTREE | 
| MyISAM | BTREE | 
| MEMORY/HEAP | HASH, BTREE | 
| NDB | HASH, BTREE (see note in text) | 
为何不用B树作为底层索引?
InnoDB选择B+树而非B树作为索引结构,核心原因是B+树的结构更贴合数据库的查询场景(尤其是范围查询)和磁盘IO效率优化:
1. B+树非叶子节点不存数据,能减少IO次数
- 
B树的问题 :B树的每个节点(包括非叶子节点)
既存储键值,也存储对应的数据(或数据地址)。这导致单个节点能容纳的键值数量很少(因为数据会占用大量空间),使得树的高度更高。例如,假设每个键值+指针占16字节,B树的一个节点(16KB)最多只能存1024个键值(16KB=16384字节 ÷16字节);若节点还要存数据,可能只能存几十甚至几个键值,树的高度会显著增加。
 - 
B+树的优势 :B+树
只有叶子节点存储数据,非叶子节点仅存储键值和指向子节点的指针(不存实际数据)。这使得非叶子节点能容纳更多键值,树的高度更低(通常3-4层即可支撑千万级数据)。同样16KB的节点,B+树的非叶子节点可存上千个键值,树高更低。而InnoDB与磁盘交互的基本单位是16KB的"页",树高越低,查询时需要加载的页(IO操作)就越少,效率自然更高。
 
2. B+树叶子节点有序相连,适合范围查询
数据库中"范围查询"(如id>100 and id<200)是高频操作,B+树对此有天然优势:
- B+树的叶子节点是有序链表 :所有叶子节点按键值
从小到大排列,且通过指针首尾相连(形成一个有序链表)。进行范围查询时,只需找到范围的起始叶子节点,然后顺着链表依次遍历即可,无需回溯上层节点。 - B树的缺陷 :B树的叶子节点之间没有关联,范围查询时需要
频繁回溯上层节点找下一个符合条件的节点,效率远低于B+树。 
聚簇索引 VS 非聚簇索引
InnoDB
InnoDB 是 MySQL 的默认存储引擎,支持事务、行级锁和外键,其索引设计与数据存储深度耦合,以聚簇索引(主键索引)为核心,辅助索引依赖聚簇索引,基于 B+ 树实现。
聚簇索引(主键索引)的 B+ 树结构
InnoDB 的聚簇索引直接将数据与索引融合 也就是数据与索引在一起(一个文件),B+ 树的叶子节点不再存储 "数据指针",而是直接存储整行数据(除主键外的其他字段内容)。
非叶子节点:存储 "主键值 + 子节点指针",仅用于定位叶子节点的位置。
叶子节点:存储 "完整行数据",且按主键顺序排序。
示例:若表 user 以 id 为主键,其聚簇索引的 B+ 树结构如下:
            
            
              bash
              
              
            
          
          非叶子节点(上层):[100, 指针A] → [200, 指针B] → [300, 指针C]
                ↓          ↓          ↓
非叶子节点(下层):[101, 指针A1] [102, 指针A2] ...  [201, 指针B1] ...
                ↓          ↓                ↓
叶子节点(数据):  (id=101, name=张三, age=20)  (id=102, name=李四, age=25)  ...  (id=201, name=王五, age=30)  ...
        
关键特点:
聚簇索引即 "数据本身",查询主键时无需额外访问数据文件(直接从 B+ 树叶子节点获取数据),效率极高。
InnoDB 要求表必须有聚簇索引:
- 若显式定义 
PRIMARY KEY,则该主键为聚簇索引; - 若无主键,选择第一个 
唯一非空索引作为聚簇索引; - 若既无主键也无唯一非空索引,InnoDB 会自动生成一个隐藏的 6 字节自增主键(DB_ROW_ID)作为聚簇索引。
 
辅助索引的 B+ 树结构
除主键外,其他字段(如 name、age)创建的索引均为辅助索引,其 B+ 树结构与聚簇索引不同:
非叶子节点:存储 "辅助索引键(如 name) + 子节点指针",用于定位叶子节点。
叶子节点:不存储整行数据,仅存储对应的主键值(如 id=101)。

查询流程:通过辅助索引查询时,需经历 "两次 B+ 树检索"(即 "回表"):
先在辅助索引的 B+ 树中,根据辅助键找到对应的主键值;
再到聚簇索引的 B+ 树中,根据主键值找到完整行数据。
示例:若为 user 表的 name 字段创建辅助索引,查询 WHERE name='张三' 的流程:
辅助索引 B+ 树:根据 name='张三' 找到叶子节点中的主键 id=101;
聚簇索引 B+ 树:根据 id=101 找到叶子节点中的完整数据 (101, 张三, 20)。
MyISAM
MyISAM 是 MySQL 早期的存储引擎,不支持事务和行级锁,仅支持表级锁,其索引设计的核心是 索引与数据完全分离(分别存储在两个文件中),所有索引(包括主键索引)均为非聚簇索引,B+ 树的叶子节点仅存储 "数据的物理地址"(而非数据本身),也就是索引与数据分离。
MyISAM 中,主键索引和辅助索引的 B+ 树结构 完全一致,仅索引键不同:
非叶子节点:存储 "索引键(主键或辅助键) + 子节点指针",用于定位叶子节点。
叶子节点:存储 "数据的物理地址"(即数据在 .MYD 文件中的偏移量,.MYD 是 MyISAM 的数据文件)。

关键特点:
索引与数据分离:索引存储在 .MYI 文件(MyISAM 索引文件),数据存储在 .MYD 文件,查询时需先通过 B+ 树找到物理地址,再到 .MYD 文件中读取数据(一次索引检索 + 一次数据检索)。
MyISAM 中主键的 "特殊之处" 在于索引结构层面无特殊地位:主键索引与普通索引的底层逻辑完全一致,叶子节点存储的都是数据在 .MYD 文件中的物理地址,而非数据本身。
请注意 :两种存储引擎的主键:强制非空且唯一 !普通索引:允许重复值,空值需看字段设置。
索引操作
创建主键索引
第一种方式
            
            
              sql
              
              
            
          
          -- 在创建表的时候,直接在字段名后指定 primary key
create table user1(id int primary key, name varchar(30));
        第二种方式:
            
            
              sql
              
              
            
          
          -- 在创建表的最后,指定某列或某几列为主键索引
create table user2(id int, name varchar(30), primary key(id));
        第三种方式:
            
            
              sql
              
              
            
          
          create table user3(id int, name varchar(30));
-- 创建表以后再添加主键
alter table user3 add primary key(id);
        主键索引的特点:
- 一个表中,最多有一个主键索引,当然可以是复合主键
 - 主键索引的效率高(主键不可重复)
 - 创建主键索引的列,它的值不能为null,且不能重复
 - 主键索引的列基本上是int类型
 
唯一索引的创建
第一种方式
            
            
              sql
              
              
            
          
          -- 在表定义时,在某列后直接指定unique唯一属性。
create table user4(id int primary key, name varchar(30) unique);
        第二种方式
            
            
              sql
              
              
            
          
          -- 创建表时,在表的后面指定某列或某几列为unique
create table user5(id int primary key, name varchar(30), unique(name));
        第三种方式
            
            
              sql
              
              
            
          
          create table user6(id int primary key, name varchar(30));
alter table user6 add unique(name);
        唯一索引的特点:
- 一个表中,可以有多个唯一索引
 - 查询效率高
 - 如果在某一列建立唯一索引,必须保证这列不能有重复数据
 - 如果一个唯一索引上指定not null,等价于主键索引
 
普通索引的创建
第一种方式
            
            
              sql
              
              
            
          
          create table user8(id int primary key,
name varchar(20),
email varchar(30),
index(name) --在表的定义最后,指定某列为索引
);
        第二种方式
            
            
              sql
              
              
            
          
          create table user9(id int primary key, name varchar(20), email 
varchar(30));
alter table user9 add index(name); --创建完表以后指定某列为普通索引
        第三种方式
            
            
              sql
              
              
            
          
          create table user10(id int primary key, name varchar(20), email 
varchar(30));
-- 创建一个索引名为 idx_name 的索引
create index idx_name on user10(name);
        普通索引的特点:
- 一个表中可以有多个普通索引,普通索引在实际开发中用的比较多
 - 如果某列需要创建索引,但是该列有重复的值,那么我们就应该使用普通索引
 
查询索引
第一种方法:show keys from 表名
            
            
              sql
              
              
            
          
          mysql> show keys from goods\G
*************************** 1. row ***************************
        Table: goods <= 表名
   Non_unique: 0 <= 0表示唯一索引
     Key_name: PRIMARY <= 主键索引
 Seq_in_index: 1
  Column_name: goods_id <= 索引在哪列
    Collation: A
  Cardinality: 0
     Sub_part: NULL
       Packed: NULL
         Null: 
   Index_type: BTREE <= 以二叉树形式的索引
      Comment: 
1 row in set (0.00 sec)
        第二种方法:show index from 表名;
第三种方法(信息比较简略):desc 表名;
删除索引
删除主键索引
            
            
              sql
              
              
            
          
          alter table 表名 drop primary key;
        删除普通索引
            
            
              sql
              
              
            
          
          mysql> alter table 表名 drop index 索引名; 索引名是 Key_name 字段
        索引创建原则
- 比较频繁作为查询条件的字段应该创建索引
 - 唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件
 - 更新非常频繁的字段不适合作创建索引
 - 不会出现在where子句中的字段不该创建索引
 
拓展内容:
下面用「通俗解释+简单例子」的方式,粗略介绍这5个索引相关概念,重点帮你理解"是什么、怎么用、实际场景中怎么体现":
1. 复合索引
解释:
普通索引是"单个列当钥匙"(比如只按age建索引),复合索引是"多个列组合当钥匙"(比如age+name)。排序规则像查字典:先按第一列排,第一列一样再按第二列排,以此类推。
举例分析:
假设给user表(有id、age、name、address列)建复合索引 (age, name):
- 索引内部排序:先按
age从小到大排(比如20、20、22、25...),同一age的再按name拼音/字母排(比如age=20时,先"张三"再"李四")。 - 能用索引的查询:
查WHERE age=20 AND name='张三'------因为索引先按age定位到20的范围,再在这个范围里按name找"张三",直接命中,不用全表扫。 (这就是索引覆盖) - 不太适合的查询:
查WHERE name='张三'------索引是先按age排的,没age条件,没法快速定位"张三",索引用不上。 
2. 索引最左匹配原则
解释:
复合索引像"一串钥匙",必须从第一把钥匙(最左列)开始用,缺了第一把,后面的钥匙都没用;如果第一把用了"范围查询"(比如age>20),后面的钥匙也会失效。
举例分析:
假设建了复合索引 (a, b, c)(对应表中a、b、c三列),看不同查询是否能用索引:
| 查询条件 | 索引使用情况 | 原因 | 
|---|---|---|
WHERE a=1 | 
用了索引的a列(部分生效) | 
从最左列a开始,能定位到a=1的范围,符合"最左前缀" | 
WHERE a=1 AND b=2 | 
用了索引的a+b列(部分生效) | 
先按a=1定位,再在这个范围里按b=2过滤,还是从最左列开始 | 
WHERE a=1 AND b=2 AND c=3 | 
用了全部a+b+c列(全生效) | 
完整匹配最左前缀,效率最高 | 
WHERE b=2 | 
索引完全没用 | 跳过了最左列a,索引是按a排的,没a就找不到b的位置 | 
WHERE a>1 AND b=2 | 
只用到a列,b列失效 | 
a>1是范围查询(不是精确匹配),a后面的b排序只在a内部有效,没法再用b过滤 | 
3. 索引覆盖
解释:
平时查数据,可能需要"先通过索引找位置,再去磁盘数据页拿完整数据"(叫"回表");如果查询需要的所有列(查的结果列+条件列)都在索引里,直接从索引拿数据就行,不用回表,速度更快。
举例分析:
还是user表,建复合索引 (age, name),看两个查询的区别:
- 
触发索引覆盖的查询 :
SELECT age, name FROM user WHERE age=20解释:查询需要的"条件列
age"和"结果列name",都在(age, name)索引里------直接从索引中提取age=20对应的所有name,不用去数据页找address等其他列,效率高。 - 
不触发索引覆盖的查询 :
SELECT age, address FROM user WHERE age=20解释:
address不在(age, name)索引里,只能先通过索引找到age=20的行位置,再回数据页拿address,多了一步"回表"操作,比上面慢。 
4. 全文索引
解释:
普通索引查"精确值"(比如age=20),like '%关键词%'查文字但慢;全文索引专门查"大段文字里的关键词",但只支持MyISAM引擎,且默认只认英文(中文查不了,需额外工具)。
举例分析:
建一个articles文章表(MyISAM引擎),存文章标题和内容:
            
            
              sql
              
              
            
          
          -- 建表+建全文索引(只支持MyISAM)
CREATE TABLE articles (
  id INT PRIMARY KEY AUTO_INCREMENT,
  title VARCHAR(200),
  body TEXT
) ENGINE=MyISAM;
-- 给title和body建全文索引
ALTER TABLE articles ADD FULLTEXT INDEX ft_art (title, body);
        - 用全文索引查询:
SELECT * FROM articles WHERE MATCH(title, body) AGAINST('database index');
解释:快速找title或body中包含"database"或"index"的文章,比WHERE title LIKE '%database%' OR body LIKE '%database%'快得多(后者会全表扫)。 - 局限:
查中文关键词(比如"数据库")会失效,因为MySQL默认全文索引不支持中文;且表必须是MyISAM引擎(InnoDB老版本不支持,新版本有改进但仍不如英文成熟)。