图解MySQL索引:从二叉树到B+树的演进之路(基础篇)

图解MySQL索引:从二叉树到B+树的演进之路(基础篇)

索引是数据库优化的利器,但你真的了解它的底层原理吗?本系列文章将分多篇深入讲解MySQL索引,本篇作为开篇,带你了解索引的概述、数据结构、分类和基本语法。

一、索引概述

1.1 什么是索引?

根据 MySQL 官方文档的定义:

Indexes are used to find rows with specific column values quickly. Without an index, MySQL must begin with the first row and then read through the entire table to find the relevant rows. The larger the table, the more this costs. If the table has an index for the columns in question, MySQL can quickly determine the position to seek to in the middle of the data file without having to look at all the data. This is much faster than reading every row sequentially.

译文: 索引用于快速查找具有特定列值的行。如果没有索引,MySQL 必须从第一行开始,然后遍历整个表以找到相关行。表越大,成本越高。如果表对相关列有索引,MySQL 可以快速确定在数据文件中间的查找位置,而无需查看所有数据。这比按顺序读取每一行要快得多。

简单来说,索引(Index)是帮助 MySQL 高效获取数据的数据结构。就像书籍的目录,通过目录可以快速定位到所需内容,而不必从头到尾翻阅整本书。

从技术角度看,索引的本质是:

  • 一种特殊的数据结构
  • 存储在磁盘或内存中
  • 以空间换时间的优化策略

1.2 索引的演示对比

为了直观感受索引的作用,我们通过一个实际案例来对比有无索引的查询差异。

假设有一张用户表user,包含100万条记录:

无索引查询:

sql 复制代码
-- 全表扫描,需要遍历100万条记录
SELECT * FROM user WHERE age = 25;
-- 查询时间:2.35秒

有索引查询:

sql 复制代码
-- 创建age字段的索引
CREATE INDEX idx_age ON user(age);

-- 再次查询
SELECT * FROM user WHERE age = 25;
-- 查询时间:0.003秒

性能提升了约783倍!这就是索引的威力。

以二叉搜索树为例

假设我们用二叉搜索树(BST,Binary Search Tree)存储数据:

markdown 复制代码
       50
      /  \
    30    70
   /  \   /  \
  20  40 60  80

查找值为60的节点:

  • 无索引:需要遍历所有节点,最坏情况需要7次比较
  • 有索引:利用二叉搜索树特性,只需3次比较:50 → 70 → 60

1.3 索引的优缺点

✅ 优点
  1. 提高查询效率:大幅减少数据扫描量,将 O(n) 线性时间的查询优化为 O(log n) 对数时间
  2. 降低排序成本:索引本身有序,可以避免额外的排序操作
  3. 加速表连接:在 JOIN(表连接)操作中,索引可以显著提升关联查询速度
  4. 保证唯一性:唯一索引可以确保数据的唯一性约束
❌ 缺点
  1. 占用存储空间:索引文件需要额外的磁盘空间
  2. 降低写操作性能:INSERT(插入)、UPDATE(更新)、DELETE(删除)时需要同步维护索引
  3. 维护成本:不合理的索引设计会适得其反
  4. 选择困难:过多索引会增加优化器的选择成本

二、索引结构

不同存储引擎支持不同的索引结构,下表展示了 MySQL 常见存储引擎的索引支持情况:

索引类型 InnoDB MyISAM Memory
B+Tree
Hash(哈希)
R-Tree(空间索引)
Full-text(全文索引) ✓(5.6+)

存储引擎说明:

  • InnoDB:MySQL 5.5+ 默认存储引擎,支持事务、行级锁、外键
  • MyISAM:早期默认引擎,不支持事务,表级锁
  • Memory:数据存储在内存中,速度快但重启后数据丢失

2.1 数据结构可视化工具

在深入学习各种索引数据结构之前,向大家推荐一个优秀的在线工具:

🎯 Data Structure Visualizations

这是由美国旧金山大学(University of San Francisco)计算机科学系官方制作的数据结构可视化网站,由 David Galles 教授开发维护。该网站提供了几乎所有常见数据结构的动态演示,包括:

  • 树结构(BST、AVL Tree、Red-Black Tree、B-Tree、B+ Tree 等)
  • 排序算法
  • 图算法
  • 哈希表

特点:

  • ✅ 完全免费,无需注册
  • ✅ 支持自定义输入数据
  • ✅ 动画演示插入、删除、查找过程
  • ✅ 可调节动画速度
  • ✅ 适合学习和教学

💡 使用建议:阅读本文时,可以打开该网站同步操作,加深理解。


2.2 二叉树及其演变

基本特性:

  • 左子树所有节点值 < 根节点值
  • 右子树所有节点值 > 根节点值

弊端:当数据按顺序插入时,会退化为链表,查询效率降至 O(n) 线性时间

markdown 复制代码
插入顺序:1, 2, 3, 4, 5
     1
      \
       2
        \
         3
          \
           4
            \
             5

在线演示: BST Visualization

图:二叉搜索树插入节点的动态过程

红黑树(Red-Black Tree)

为解决 BST 退化问题,红黑树通过自平衡机制保持树高度平衡。

红黑树特性:

  • 每个节点是红色或黑色
  • 根节点是黑色
  • 所有叶子节点(NIL)是黑色
  • 红色节点的两个子节点都是黑色
  • 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点

示例:插入递增序列

scss 复制代码
插入顺序:10, 20, 30, 40, 50
       30(B)
      /     \
   20(B)    40(B)
   /          \
10(R)        50(R)

(B=黑色节点, R=红色节点)

对比 BST 退化为链表,红黑树通过旋转和重新着色保持平衡,树高度始终保持在 O(log n)。

在数据库场景下的问题:

  • 树的高度较高,磁盘 I/O(Input/Output,输入输出)次数多
  • 每个节点只存储一个元素,空间利用率低

在线演示: Red-Black Tree Visualization

图:红黑树插入节点后的自动平衡调整过程


2.3 B-Tree(B树,Balanced Tree 平衡树)

B-Tree 是为磁盘等外存储介质设计的多路平衡查找树

核心特性:

  • 每个节点可以存储多个关键字(度数 m)
  • 所有叶子节点在同一层
  • 非叶子节点存储数据和指针
  • 节点中的关键字从左到右递增排列

最大度数(Order/Degree): 一个节点最多能拥有的子节点数量。度数越大,树的高度越低,I/O 次数越少。

示例(5阶B-Tree):

css 复制代码
           [10, 20, 30, 40]
          /    |    |    |   \
     [1-9] [11-19] [21-29] [31-39] [41+]

在线演示: B-Tree Visualization

图:B-Tree 插入节点及节点分裂的动态过程

💡 操作建议:在可视化网站中,尝试依次插入 10, 20, 30, 40, 50, 60,观察节点分裂过程。


2.4 B+Tree(B+树,B-Tree 的升级版)

B+Tree 是 B-Tree 的改进版本,也是 MySQL InnoDB 存储引擎的默认索引结构。

核心改进:
  1. 所有数据存储在叶子节点

    • 非叶子节点只存储索引键和指针
    • 大大增加了每个节点能存储的键数量
  2. 叶子节点形成有序链表

    • 相邻叶子节点通过指针连接
    • 支持高效的范围查询
  3. 更高的扇出(Fanout,分支因子)

    • 树的高度更低
    • 减少磁盘 I/O 次数

示例:4阶 B+Tree 结构

ini 复制代码
插入顺序:3, 5, 7, 10, 12, 15, 20, 22, 25, 30, 35, 40
度数:4(Max Degree = 4)

非叶子节点:    [10] [20] [30]
                /    |    |    \
叶子节点:   [3,5,7] → [12,15] → [22,25] → [35,40]
            (双向链表连接)

在线演示: B+ Tree Visualization

图:B+Tree 插入节点及叶子节点链表维护过程

💡 B-Tree vs B+Tree 对比:在可视化网站中同时打开 B-Tree 和 B+Tree,输入相同的数据序列,对比两者的区别:

  • B+Tree 的非叶子节点不存储数据,只作为索引
  • B+Tree 的所有数据都在叶子节点,且叶子节点之间有链表连接
  • B+Tree 更适合范围查询(如:查找 10-50 之间的所有值)

2.5 MySQL中的B+Tree索引

InnoDB存储引擎的实现特点:

聚簇索引(Clustered Index):

  • 叶子节点存储完整的行记录
  • 主键即聚簇索引
  • 一张表只有一个聚簇索引
css 复制代码
主键索引(聚簇索引):
          [10]
         /    \
    [5]        [15]
   /   \      /    \
[1,2,3] [6,7] [11,12] [16,17]
  ↓      ↓      ↓       ↓
完整行数据 完整行数据 完整行数据 完整行数据

二级索引(Secondary Index):

  • 叶子节点存储索引列值和主键值
  • 需要回表查询获取完整数据
css 复制代码
二级索引(age字段):
          [25]
         /    \
    [20]       [30]
   /   \      /    \
[18,19] [22,23] [27,28] [32,35]
  ↓      ↓      ↓       ↓
主键值  主键值  主键值   主键值

2.6 Hash 索引(哈希索引/散列索引)

原理: 通过哈希函数(Hash Function)将键值转换为哈希码,直接定位数据位置。

特点:

  • ✓ 等值查询效率极高(O(1) 常数时间)
  • ✗ 不支持范围查询
  • ✗ 不支持排序
  • ✗ 不支持模糊查询(LIKE)

应用场景: Memory 存储引擎,适合等值查询场景。

sql 复制代码
-- Hash索引适用
SELECT * FROM user WHERE id = 100;

-- Hash索引不适用
SELECT * FROM user WHERE age > 20;
SELECT * FROM user WHERE name LIKE 'Zhang%';

2.7 为什么InnoDB选择B+Tree?

综合对比各种数据结构:

数据结构 查询效率 范围查询 排序 磁盘I/O 空间利用率
二叉树 O(log n) 支持 支持
红黑树 O(log n) 支持 支持 较高
B-Tree O(log n) 支持 支持
B+Tree O(log n) 优秀 优秀 最低 最高
Hash O(1) 不支持 不支持 最低

InnoDB 选择 B+Tree 的核心原因:

  1. 树高度低(通常3-4层),磁盘 I/O 次数少
  2. 叶子节点链表结构,范围查询高效
  3. 非叶子节点不存数据,扇出更大
  4. 支持排序(ORDER BY)和分组(GROUP BY)操作
  5. 稳定的 O(log n) 对数时间查询性能

三、索引分类

MySQL 索引根据不同维度有多种分类方式:

3.1 按功能逻辑分类

索引类型 说明 关键字 特点
主键索引(Primary Key Index) 针对主键创建 PRIMARY KEY 唯一且非空,一张表只能有一个
唯一索引(Unique Index) 避免数据列重复 UNIQUE 允许 NULL(空值),可以有多个
普通索引(Normal Index) 快速定位数据 INDEX/KEY 最基本的索引
全文索引(Full-text Index) 文本内容搜索 FULLTEXT 用于文本检索(MySQL 5.6+)

3.2 按物理存储分类

1. 聚簇索引(Clustered Index,也称聚集索引)
  • 数据和索引存储在一起
  • 叶子节点保存完整行数据
  • InnoDB 必有且只有一个聚簇索引

选择规则:

markdown 复制代码
1. 如果有主键 → 主键索引即聚簇索引
2. 如果没有主键 → 第一个 UNIQUE NOT NULL 索引即聚簇索引  
3. 都没有 → InnoDB 自动生成隐藏的 row_id 作为聚簇索引
2. 二级索引(Secondary Index,也称辅助索引/非聚簇索引)
  • 索引和数据分开存储
  • 叶子节点存储索引列值 + 主键值
  • 查询时可能需要回表查询(Table Lookup)

3.3 什么是回表查询?

回表查询指的是通过二级索引找到主键值后,再通过主键索引(聚簇索引)查询完整行数据的过程。

示例:

sql 复制代码
-- 表结构
CREATE TABLE user (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    age INT,
    INDEX idx_age(age)
);

-- 查询语句
SELECT * FROM user WHERE age = 25;

执行过程:

ini 复制代码
步骤1:扫描二级索引idx_age,找到age=25的记录
       得到主键值:id=10, id=20, id=35

步骤2:回表查询(3次)
       根据id=10,在聚簇索引中查询完整行数据
       根据id=20,在聚簇索引中查询完整行数据  
       根据id=35,在聚簇索引中查询完整行数据

步骤3:返回结果集

3.4 思考题

问题:如何避免回表查询,提升查询性能?

点击查看答案

答案:使用覆盖索引(Covering Index)

覆盖索引是指查询的所有列都包含在索引中,无需回表。

sql 复制代码
-- 创建联合索引(Composite Index,也称复合索引)
CREATE INDEX idx_age_name ON user(age, name);

-- 这个查询会使用覆盖索引,无需回表
SELECT id, age, name FROM user WHERE age = 25;

-- EXPLAIN 结果中 Extra 列会显示:Using index

优化建议:

  1. 查询时只 SELECT 需要的字段,避免 SELECT *
  2. 合理设计联合索引,覆盖高频查询列
  3. 利用索引下推(ICP,Index Condition Pushdown)优化

四、索引语法

SQL:Structured Query Language,结构化查询语言

4.1 创建索引

方式一:创建表时定义(CREATE TABLE)
sql 复制代码
CREATE TABLE user (
    id INT PRIMARY KEY,                    -- 主键索引
    username VARCHAR(50) UNIQUE,           -- 唯一索引
    email VARCHAR(100),
    age INT,
    status TINYINT,
    INDEX idx_age(age),                    -- 普通索引
    INDEX idx_age_status(age, status),     -- 联合索引/复合索引
    FULLTEXT idx_email(email)              -- 全文索引
);
方式二:在已有表上创建(CREATE INDEX)
sql 复制代码
-- 创建普通索引
CREATE INDEX idx_username ON user(username);

-- 创建唯一索引
CREATE UNIQUE INDEX idx_email ON user(email);

-- 创建联合索引
CREATE INDEX idx_age_status ON user(age, status);

-- 创建全文索引
CREATE FULLTEXT INDEX idx_content ON article(content);
方式三:使用 ALTER TABLE(修改表结构)
sql 复制代码
-- 添加主键索引
ALTER TABLE user ADD PRIMARY KEY(id);

-- 添加唯一索引
ALTER TABLE user ADD UNIQUE INDEX idx_email(email);

-- 添加普通索引
ALTER TABLE user ADD INDEX idx_age(age);

-- 添加全文索引
ALTER TABLE user ADD FULLTEXT INDEX idx_content(content);

4.2 查看索引(SHOW INDEX)

sql 复制代码
-- 查看表的所有索引
SHOW INDEX FROM user;

-- 使用 \G 格式化显示(纵向显示,更易阅读)
SHOW INDEX FROM user\G

\G 说明:MySQL 客户端中的格式化输出符号,将横向表格改为纵向键值对显示

重要字段说明:

字段名 说明
Key_name 索引名称(PRIMARY 表示主键索引)
Seq_in_index 在联合索引中的顺序(从 1 开始)
Column_name 索引列名称
Index_type 索引类型(BTREE/HASH/FULLTEXT 等)
Non_unique 0 = 唯一索引,1 = 非唯一索引
Cardinality 索引基数(不重复值的估算数量,越大区分度越好)

示例:

markdown 复制代码
mysql> SHOW INDEX FROM user\G
*************************** 1. row ***************************
     Key_name: PRIMARY
  Column_name: id
   Index_type: BTREE
   Non_unique: 0
*************************** 2. row ***************************
     Key_name: idx_age_status
 Seq_in_index: 1
  Column_name: age
   Index_type: BTREE
   Non_unique: 1

4.3 删除索引(DROP INDEX)

sql 复制代码
-- 方式一:使用 DROP INDEX
DROP INDEX idx_age ON user;

-- 方式二:删除主键索引
ALTER TABLE user DROP PRIMARY KEY;

-- 方式三:使用 ALTER TABLE 删除索引
ALTER TABLE user DROP INDEX idx_age;

注意:删除索引前请确认该索引不再使用,避免影响查询性能

4.4 实战案例

案例:性能对比

**场景:**用户表有100万数据,经常根据年龄和状态查询活跃用户。

sql 复制代码
-- 原始查询(全表扫描)
SELECT * FROM user WHERE age BETWEEN 20 AND 30 AND status = 1;
-- 执行时间:1.8秒

-- 创建联合索引
CREATE INDEX idx_age_status ON user(age, status);

-- 再次查询(使用索引)
SELECT * FROM user WHERE age BETWEEN 20 AND 30 AND status = 1;
-- 执行时间:0.015秒

性能提升120倍! 这就是合理使用索引的威力。

使用 \G 格式化输出

在查看索引信息时,使用 \G 可以将横向输出改为纵向,更易读:

sql 复制代码
-- 横向输出(表格形式,字段多时不易读)
SHOW INDEX FROM user;

-- 纵向输出(键值对形式,推荐)
SHOW INDEX FROM user\G

输出示例:

markdown 复制代码
*************************** 1. row ***************************
        Table: user                    -- 表名
   Non_unique: 1                       -- 是否非唯一
     Key_name: idx_age_status          -- 索引名
 Seq_in_index: 1                       -- 列在索引中的序号
  Column_name: age                     -- 列名
   Index_type: BTREE                   -- 索引类型

说明\G 是 MySQL 命令行客户端的特殊命令,将查询结果按行纵向显示


五、总结

本文作为 MySQL 索引系列的开篇,系统讲解了索引的基础知识:

  1. 索引概述:理解索引的本质是数据结构,通过对比演示看到索引带来的性能提升
  2. 索引结构:从二叉树到红黑树,再到 B-Tree 和 B+Tree 的演变,理解 InnoDB 为什么选择 B+Tree
  3. 索引分类:掌握聚簇索引、二级索引的区别,理解回表查询的概念
  4. 索引语法:学会索引的创建、查看、删除等基本操作

核心要点:

  • 索引是以空间换时间的优化策略
  • B+Tree 通过更低的树高度和叶子节点链表,实现了高效的查询和范围扫描
  • 理解索引的物理存储结构,才能更好地优化查询
  • 时间复杂度:O(n) 线性时间 > O(log n) 对数时间 > O(1) 常数时间

术语表:

  • BST:Binary Search Tree,二叉搜索树
  • I/O:Input/Output,输入输出操作
  • SQL:Structured Query Language,结构化查询语言
  • ICP:Index Condition Pushdown,索引条件下推

参考资料

技术文档

可视化工具

辅助工具

本文使用 markdown.com.cn 排版

相关推荐
蛋先生DX3 小时前
AI 友好的云开发 MySQL SDK 它来了!微信小程序能直连关系型数据库了
mysql·ai编程·小程序·云开发
IT教程资源C5 小时前
(N_156)基于springboot,vue小区物业管理系统
mysql·vue3·前后端分离·springboot小区物业
半路_出家ren5 小时前
MySQL数据库,DDL,DML,查询,权限,主从复制
数据库·mysql·主从复制·权限·ddl·dml
运维成长记5 小时前
Mysql的数据备份和高可用
数据库·mysql
冻咸鱼11 小时前
MySQL基础知识大全
数据库·mysql·oracle
hskxkj14 小时前
MySQL第三次作业
数据库·mysql
冷崖17 小时前
MySQL-TrinityCore异步连接池的学习(七)
学习·mysql
程序新视界18 小时前
详解MySQL两种存储引擎MyISAM和InnoDB的优缺点
数据库·后端·mysql
半路_出家ren18 小时前
设计一个学生管理系统的数据库
linux·数据库·sql·mysql·网络安全·数据库管理员