MySQL 索引 B + 树机制深度剖析

目录

一、索引

为什么一定要用索引?

二、为什么 MySQL 偏偏选 B+树

1.HASH(哈希)

2.二叉搜索树(BST / AVL / 红黑树)

3.N 叉树

4.B+ 树:最终答案

B+ 树 vs B 树,强在哪?

三、深挖底层:MySQL 中的"页"(Page)

1.为什么要用"页"?

2.页文件头和页文件尾

3.页主体

4.页目录:从"从头遍历"到"二分查找"

5.数据页头

四、B+ 树在 MySQL 索引中的应用

1.检索过程:一次查询要走几步?​编辑

2.算一笔账:三层树高的 B+ 树能存多少数据?

五、六种索引分类

聚集索引 vs 非聚集索引,重点理解这两个概念

引出两个高频面试词:回表 与 索引覆盖

六、索引的使用:自动 vs 手动

1.自动创建

2.手动创建

3.创建复合索引(多列,逗号隔开)

4.查看索引

5.删除索引

七、创建索引的注意事项


一、索引

MySQL 的索引,本质是一种数据结构,用来帮助数据库高效地查询、更新表中的数据。它按一定规则把表里的记录"排好序",这样查询时只要在索引里找,而不是把整张表翻一遍。

最形象的类比就是字典目录:汉语字典前面有按笔画、偏旁部首、拼音排序的目录页。你要查一个"字",不会从第一页翻到最后一页,而是先翻目录,定位到这个字大概在哪一页,再跳过去。索引就扮演了这个"目录"的角色——它指向数据行所在的位置,帮我们快速定位。

为什么一定要用索引?

显而易见,使用索引的目的只有一个,就是提升数据检索的效率,在应用程序的运行过程中,查询操作的频率远远高于增删改的频率。


二、为什么 MySQL 偏偏选 B+树

1.HASH(哈希)

  • 优点:理论上查询时间复杂度是O(1),极快,等值查找无敌。
  • 缺点:查找的时候只能进行"相等"匹配,不能进行 > < 或者范围匹配

2.二叉搜索树(BST / AVL / 红黑树)

二叉搜索树中序遍历出来是有序数组,听起来不错,但有几个硬伤:

  1. 最坏情况退化成链表:插入数据恰好有序时,树高变成 N,时间复杂度退化为O(N)。
  2. 节点个数多,树高不可控:哪怕用 AVL、红黑树保持平衡,它俩毕竟是二叉树,一个节点最多两个子节点,数据一多树还是很"高"。
  3. 致命的 IO 问题:索引数据持久化存储在磁盘中,每向下访问一个子节点,就可能触发一次磁盘 IO。而磁盘读写速度远慢于内存访问,是数据库的核心性能瓶颈。树的高度越高,查询途经的节点越多,IO 次数就越多,查询性能也就越差。

3.N 叉树

为了解决树高问题,很自然的思路就是——一个节点多存几个子节点,即N 叉树。

相同数据量下,N 叉树的树高被有效压低了,也就意味着减少磁盘 IO 次数,从而提升效率。但 MySQL 认为 N 叉树还不够好,最后选择了它的"进阶版"——B+ 树。

4.B+ 树:最终答案

B+ 树是常用于数据库和文件系统的平衡查找树,也是MySQL 索引实际采用的数据结构。

B+ 树的三个核心特点:

特点

说明

数据稳定有序

能保持数据有序,插入和修改的时间复杂度稳定

非叶子只做索引

非叶子节点仅存储索引(key),不存真实数据;所有真实数据都保存在叶子节点

叶子组成有序链表

所有叶子节点构成一个有序链表,可按 key 排序依次遍历全部数据

B+ 树 vs B 树,强在哪?

对比项

B 树

B+ 树

非叶子节点

也存数据

只存索引,不存数据 → 一页能装更多 key,树更矮

叶子节点数据

分散在各层

连续且互相链接,便于区间查找和搜索

查找性能

有的层有数据、有的没有,不均匀

相同树高下查找任一元素的时间复杂度一样,性能均衡

非叶子值归属

数据只存一处

非叶子节点的值都包含在叶子节点中,查询路径统一

一句话记住:B+ 树把"索引"和"数据"彻底分离——上层只做"路标",真正的内容全部堆在底层叶子,再用链表串起来。这让它既矮(IO 少)又支持范围查询(链表好遍历)。


三、深挖底层:MySQL 中的"页"(Page)

讲 B+ 树光看树形结构是虚的,得落到 MySQL 物理存储上。这里有个关键概念——页。

1.为什么要用"页"?

页是内存与磁盘交互的最小单元,默认大小 16KB。每次内存和磁盘交互,至少读取一页。

为什么一次要读 16KB 这么大一块?核心依据是局部性原理:

  • 时间局部性:某个数据刚被访问过,近期很可能再次被访问。
  • 空间局部性:将来要用的数据,大概率与正在使用的数据在空间地址上临近。

所以一次读一整页进内存:当下次查询的数据还在这页里时,就能直接从内存读取,减少磁盘 IO,性能自然提升。

查看页大小的命令:

SHOW VARIABLES LIKE 'innodb_page_size';

在MySQL中有多种不同类型的页,最常⽤的就是⽤来存储数据和索引的"索引⻚",也叫做"数据
页",数据页的基本结构如下图所示:

2.页文件头和页文件尾

不论哪种类型的页,都包含页头(File Header)和页尾(File Trailer),主体用"数据行"填充。整体结构大概是:

  • 页头 / 页尾:包含元信息。我们只关注两个——上一页页号、下一页页号,靠它们把页与页链接起来,形成双向链表。

3.页主体

保存真实数据的主要区域。一个新页会自动分配页内最小行和页内最大行作为链表的头和尾。

中间的每行记录"下一行的地址偏移量",从而组成一个单向链表。数据按主键从小到大的顺序链接。

4.页目录:从"从头遍历"到"二分查找"

如果按主键找一条数据,最笨的方法是从头行开始沿链表逐个比对。但一个页有 16KB,通常有几百行数据,每次遍历几百行,效率太低。

InnoDB 的优化是:在每个页里加一个页目录结构——

  • 把页内所有行(含头行、尾行)分组,头行单独一组,其他每组最多8 条;
  • 把每组最后一行在页中的地址,按主键从小到大记录在页目录里;
  • 页目录里的每个位置叫一个槽,每个槽对应一个分组;组内超过 8 行就分裂出新分组。

查询某行时,先用二分查找定位到对应的槽,再在槽内最多 8 行里遍历。这样查询效率大幅提升。

举例:要找主键为 6 的行,先比对各槽记录的主键值,定位到"槽 2",再从槽 2 的第一条记录开始遍历,第二条就是目标行。

5.数据页头

数据页头记录了当前页保存数据相关的信息,如下图所示


四、B+ 树在 MySQL 索引中的应用

1.检索过程:一次查询要走几步?

在 InnoDB 里,非叶子节点保存索引数据,叶子节点保存真实数据。

假设要找 id = 5 的记录,完整流程是:

  • 先看 B+ 树根节点的索引记录,5 小于某个分界值 → 访问左孩子,加载索引页 2;
  • 在索引页 2 里判断 id 大小,找到等于 5 的记录,命中;
  • 加载对应的数据页。

三次磁盘 IO 的过程:加载索引页1 → 加载索引页2 → 加载数据页3。

2.算一笔账:三层树高的 B+ 树能存多少数据?

数据页(叶子节点):假设单行数据占 1KB,忽略页头页尾开销,一页 16KB 大约可以存 16 条完整数据。

索引页(非叶子节点):每条索引项 = 主键(BIGINT 占 8 字节)+ 下一页地址(6 字节)= 14 字节。一页 16KB 大约可以存16×1024 ÷ 14 ≈ 1170条索引记录。

于一棵三层的 B + 树(根节点 + 一层索引节点 + 叶子节点):

  • 根节点可以指向约 1170 个二级索引页;
  • 每个二级索引页可以指向约 1170 个数据页;
  • 总数据量 = 1170 × 1170 × 16 ≈2190 万条。

这就是"矮树"的回报:IO 次数基本等于树高。树矮 = IO 少 = 快。两千万数据只要 3 次 IO,性能自然起飞。


五、六种索引分类

索引类型

说明

关键点

主键索引

表定义了主键,InnoDB 就用它做聚集索引

推荐每个表都定义主键,没有合适的就用自增列

普通索引

最基本的索引,没有唯一性限制

可为多列创建,此时叫复合索引/组合索引

唯一索引

与普通索引类似,但列不允许重复值

表定义了唯一键时自动创建

全文索引

基于文本列(CHAR/VARCHAR/TEXT)创建

用于全文搜索,仅 MyISAM 和 InnoDB 支持

聚集索引

与主键索引基本是同义词

数据行的物理顺序与索引顺序一致,索引即数据

非聚集索引

聚集索引以外的索引,也叫二级索引

每条记录包含主键列 + 二级索引指定列

聚集索引 vs 非聚集索引,重点理解这两个概念

  • 聚集索引(主键索引):叶子节点直接保存真实数据行。表没有主键时,InnoDB 会用第一个非空唯一列作为聚集索引;再没有,就生成一个 6 字节、单调递增的隐藏行号当索引。
  • 非聚集索引(二级索引):叶子节点保存的是主键列 + 二级索引指定的列,而不是真实数据。
引出两个高频面试词:回表 与 索引覆盖
  • 回表查询:通过二级索引查到主键后,再用主键去聚集索引里"走一遍"拿真实数据,这个过程就叫回表。多了一次 IO。
  • 索引覆盖:如果一条 SELECT 用了普通索引,且查询的列恰好都在这个索引里,那么不用回表,直接从索引返回数据。这叫索引覆盖,是优化查询的常用手段。

实战感悟:设计索引时,如果能把高频查询的列"塞进"同一个复合索引里,让查询走索引覆盖、避免回表,是性价比非常高的优化点。


六、索引的使用:自动 vs 手动

1.自动创建

  • 给表加主键约束、外键约束、唯一约束时,MySQL 会为对应列自动创建索引。
  • 如果表没指定任何约束,MySQL 也会为每列自动生成一个索引并用行号标识。

2.手动创建

① 主键索引

# 方式一,创建表时创建主键 create table t_test_pk ( id bigint primary key auto_increment,-- 这里的 primary key (id) 同时创建了主键约束 + 主键索引 name varchar(20) ); # 方式二,创建表时单独指定主键列 create table t_test_pk1 ( id bigint auto_increment, name varchar(20), primary key (id)-- 这里的 primary key (id) 同时创建了主键约束 + 主键索引 ); # 方式三,建表后通过ALTER语句追加创建 create table t_test_pk2 ( id bigint, name varchar(20) ); alter table t_test_pk2 add primary key (id) ; alter table t_test_pk2 modify id bigint auto_increment;

② 唯一索引

# 方式一, 创建表时创建唯一键 create table t_test_uk ( id bigint primary key auto_increment, name varchar(20) unique ); 方式二, 创建表时单独指定唯一列 create table t_test_uk1 ( id bigint primary key auto_increment, name varchar(20), unique (name) ); 方式三,建表后,修改表中的列为唯一索引 create table t_test_uk2 ( id bigint primary key auto_increment, name varchar(20) ); alter table t_test_uk2 add unique (name) ;

③ 普通索引

# 方式一,创建表时指定索引列 create table t_test_index ( id bigint primary key auto_increment, name varchar(20) unique sno varchar(10), index(sno) ); #方式二,修改表中的列为普通索引 create table t_test_index1 ( id bigint primary key auto_increment, name varchar(20), sno varchar(10) ); alter table t_test_index1 add index (sno) ; # 方式三,单独创建索引并指定索引名 create table t_test_index2 ( id bigint primary key auto_increment, name varchar(20), sno varchar(10) ); create index index_name on t_test_index2(sno);

3.创建复合索引(多列,逗号隔开)

创建语法与创建普通索引相同,只不过指定多个列,列与列之间用逗号隔开

# 方式一,创建表时指定索引列 create table t_test_index4 ( id bigint primary key auto_increment,-- 主键索引(单字段,不是复合) name varchar(20), sno varchar(10), class_id bigint, index (sno, class_id)-- 这里就是复合索引:一个索引包含 sno + class_id 两个字段 ); # 方式二,修改表中的列为复合索引 create table t_test_index5 ( id bigint primary key auto_increment, name varchar(20), sno varchar(10), class_id bigint ); alter table t_test_index5 add index (sno, class_id); # 方式三,单独创建索引并指定索引名 create table t_test_index6 ( id bigint primary key auto_increment, name varchar(20), sno varchar(10), class_id bigint ); create index index_name on t_test_index6 (sno, class_id);--index_name复合索引有sno, class_id 2个字段

4.查看索引

# 方式一:show keys from 表名 show keys from t_test_index6; # 方式二 show index from t_test_index6; # 方式三,简要信息:desc 表名; desc t_test_index6;

5.删除索引

①删除主键(注意:若列有自增属性需先去掉)

alter table 表名 drop primary key;

例子:

②删除其他索引

alter table 表名 drop index 索引名;

踩坑提醒:删除主键时如果该列是 AUTO_INCREMENT,会报错 ERROR 1075(不能只有一个自增列且必须定义为主键)。正确顺序是先去掉自增属性,再删主键。


七、创建索引的注意事项

  1. 加在高频查询的列上:索引要建在经常被 WHERE、JOIN、ORDER BY 用到的列,而不是随手建。
  1. 索引占额外存储空间:每个索引都是一棵 B+ 树,都要占磁盘空间。
  1. 增删改会同时维护索引:每次插入、更新、删除,都要同步修改索引,可能影响写性能。
  1. 过犹不及:创建过多或不合理的索引会导致性能下降,需要谨慎选择和规划。

关于索引的优化与使用细节(最左前缀、覆盖索引的实战、EXPLAIN 分析等),留到 MySQL 中级课程再深入。

相关新闻

读懂 AS300 激光对中仪|偏移角(角偏差)到底代表什么?

读懂 AS300 激光对中仪|偏移角(角偏差)到底代表什么?

很多现场师傅做完激光对中&#xff0c;看得懂平行偏移&#xff0c;却看不懂偏移角&#xff08;角偏差&#xff09;。明明径向数值调得很小&#xff0c;设备依旧振动大、联轴器弹性体频繁开裂&#xff0c;多半就是偏移角超标埋下的隐患。AS300 激光对中仪完成盘车测量后&#xf…

2026/10/20 11:01:58 阅读更多 →
把Kronos-Tokenizer-base集成进量化流程:离散Token ID在K线时序策略中的落地指南

把Kronos-Tokenizer-base集成进量化流程:离散Token ID在K线时序策略中的落地指南

人工智能基础模型金融科技 【免费下载链接】kronos-tokenizer-base-npu 用户可在昇腾 NPU 上直接运行 Kronos-Tokenizer-base 金融 K 线时间序列模型&#xff0c;将 OHLCV 等连续数据转换为离散 Token ID&#xff0c;支持离线推理、CPU/NPU 一致性验证与性能基准测试&#xff0…

2026/10/20 11:01:58 阅读更多 →
企业微信自动回复在哪里设置?微客AI助手按角色分权的三步落位

企业微信自动回复在哪里设置?微客AI助手按角色分权的三步落位

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 11:01:58 阅读更多 →

最新新闻

提示词模板化:从基础到高级的Prompt Engineering实战指南

提示词模板化:从基础到高级的Prompt Engineering实战指南

1. 为什么要把提示词做成模板&#xff1a;从重复劳动到可复用资产先讲一个我自己的经历。早期用对话式AI干活的时候&#xff0c;每次都是临时想提示词&#xff0c;想到哪写到哪&#xff1a;“帮我总结一下这段文字”“写一个产品介绍&#xff0c;要专业一点”。这种随手写的做法…

2026/10/20 12:46:33 阅读更多 →
Kafka入门进阶:分区、副本、acks与消费者组核心机制详解

Kafka入门进阶:分区、副本、acks与消费者组核心机制详解

1. 从“能跑起来”到“跑得明白”&#xff1a;Kafka 基础入门的第二道坎很多人第一次接触 Kafka&#xff0c;都是照着官方 Quickstart 敲一遍命令行&#xff0c;看到生产者发消息、消费者收到消息&#xff0c;就觉得“我会了”。但真到了项目里&#xff0c;面对一堆server.prop…

2026/10/20 12:46:33 阅读更多 →
SpringBoot+Vue图书推荐系统源码解析:协同过滤与前后端分离实战

SpringBoot+Vue图书推荐系统源码解析:协同过滤与前后端分离实战

最近我在帮几位同学梳理毕业设计项目&#xff0c;被问得最多的就是“图书推荐系统”。这类题到现在依旧热门&#xff0c;原因也很简单&#xff1a;图书天然适合做推荐&#xff0c;用户行为数据容易理解&#xff0c;和电商推荐、短视频推荐比起来业务逻辑更干净&#xff0c;特别…

2026/10/20 12:46:33 阅读更多 →
技能不是标签而是动作:用刻意练习、反馈与复盘构建底层能力

技能不是标签而是动作:用刻意练习、反馈与复盘构建底层能力

很多人都把“技能”当成一个名词来理解&#xff0c;觉得自己会写代码、会做PPT、会剪辑视频&#xff0c;就算拥有一项技能。但真正带过人、自己从零啃过几门硬技能的人会知道&#xff0c;技能其实是动词——它是你每天都要做的一个动作&#xff0c;而不是简历上既能唬人又能自我…

2026/10/20 12:46:33 阅读更多 →
把涂鸦设备搬回局域网:Tuya-Local 本地控制与 Home Assistant 配置指南

把涂鸦设备搬回局域网:Tuya-Local 本地控制与 Home Assistant 配置指南

把涂鸦设备搬回局域网&#xff1a;Tuya-Local 本地控制与 Home Assistant 配置指南 【免费下载链接】tuya-local Local support for Tuya devices in Home Assistant 项目地址: https://gitcode.com/GitHub_Trending/tu/tuya-local Tuya-Local 是 Home Assistant 的一个…

2026/10/20 12:46:33 阅读更多 →
MCP协议驱动智能体底座架构设计:从插件思维到接口思维

MCP协议驱动智能体底座架构设计:从插件思维到接口思维

1. 从“插件思维”到“接口思维”的认知跃迁大多数人第一次接触MCP&#xff08;Model Context Protocol&#xff0c;模型上下文协议&#xff09;时&#xff0c;脑子里蹦出来的第一个词就是“插件系统”。这也不奇怪&#xff0c;毕竟它的外在表现确实像插件&#xff1a;你写一个…

2026/10/20 12:45:32 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →