【第四章】索引——B+树、回表,加快数据库的查找能力的利器

【第四章】索引------B+树、回表、最左前缀,把查询提速

  • [1. 索引是什么](#1. 索引是什么)
  • [2. 为什么要用索引(以及索引的代价)](#2. 为什么要用索引(以及索引的代价))
  • [3. 索引为什么不用 Hash](#3. 索引为什么不用 Hash)
  • [4. 为什么是 B+ 树(不是二叉树、不是红黑树)](#4. 为什么是 B+ 树(不是二叉树、不是红黑树))
  • [5. MySQL 的"页"(Page):16KB 的世界观](#5. MySQL 的“页”(Page):16KB 的世界观)
  • [6. InnoDB 的两类核心索引:聚簇索引 & 二级索引](#6. InnoDB 的两类核心索引:聚簇索引 & 二级索引)
    • [6.1 聚簇索引(Clustered Index)](#6.1 聚簇索引(Clustered Index))
    • [6.2 二级索引(Secondary Index)](#6.2 二级索引(Secondary Index))
    • [6.3 索引覆盖(Covering Index)](#6.3 索引覆盖(Covering Index))
  • [7. 索引分类](#7. 索引分类)
    • [7.1 主键索引(PRIMARY KEY)](#7.1 主键索引(PRIMARY KEY))
    • [7.2 普通索引(INDEX / KEY)](#7.2 普通索引(INDEX / KEY))
    • [7.3 唯一索引(UNIQUE)](#7.3 唯一索引(UNIQUE))
    • [7.4 全文索引(FULLTEXT)](#7.4 全文索引(FULLTEXT))
  • [8. 索引怎么用:自动创建、手动创建、查看、删除](#8. 索引怎么用:自动创建、手动创建、查看、删除)
    • [8.1 哪些情况会自动创建索引](#8.1 哪些情况会自动创建索引)
    • [8.2 手动创建索引(几种方式)](#8.2 手动创建索引(几种方式))
    • [8.3 查看索引](#8.3 查看索引)
    • [8.4 删除索引(别把自己绕进去)](#8.4 删除索引(别把自己绕进去))
  • [9. 复合索引:最左前缀](#9. 复合索引:最左前缀)
  • [10. EXPLAIN:你判断"到底走没走索引"的方式](#10. EXPLAIN:你判断“到底走没走索引”的方式)
  • [11. 一些非常常见的"索引失效写法"](#11. 一些非常常见的“索引失效写法”)
    • [11.1 对索引列做函数/运算](#11.1 对索引列做函数/运算)
    • [11.2 LIKE 左边带 %](#11.2 LIKE 左边带 %)
    • [11.3 复合索引跳过最左列](#11.3 复合索引跳过最左列)
  • 本章小结

到这一步你会发现一个很真实的现象:SQL 写对不难,写快才难

而在 MySQL 里,查询性能最绕不开的一块,就是索引。

这一章之前会遇到的问题":

  • 索引到底是什么,为什么它能加速
  • 为什么 MySQL 选 B+ 树,不选 Hash
  • InnoDB 的聚簇索引 / 二级索引到底有什么区别(以及"回表"是什么)
  • 索引怎么建、怎么查、怎么删,复合索引怎么用
  • 常见写法为什么会让索引失效(你明明建了索引但还是全表扫)
    剩下的问题就在这一章来解决。

1. 索引是什么

有个比喻我觉得特别贴:索引像书的目录。

  • 你不看目录:找一段内容只能从第一页翻到最后一页(全表扫描)
  • 你看目录:先定位大概位置,再直接翻过去(走索引)

在 MySQL 里更准确一点说,索引是一种数据结构,它把"列的值"按某种规则组织起来,并且能快速定位到对应的数据行。

你会发现一个关键点:索引的价值几乎都在"读"

因为插入/更新/删除时,索引也要跟着维护,索引越多,写入成本越高。


2. 为什么要用索引(以及索引的代价)

索引的目标只有一个:提升检索效率。

但它不是"白加速",它有明确代价:

  • 占空间:索引本质也是一份数据结构文件
  • 降低写入效率:INSERT/UPDATE/DELETE 都要改索引
  • 选错会反噬:建了不合理的索引,反而可能让优化器走了更差的执行计划

所以索引不是"越多越好",而是"该建的建,不该建的别硬上"。


3. 索引为什么不用 Hash

核心原因很直接:Hash 不支持范围查找

Hash 的优势是等值查询快(理想情况下接近 O(1)),但一旦你写:

sql 复制代码
WHERE created_at BETWEEN ... AND ...

Hash 的结构就没法优雅地做范围扫描了。

而数据库里范围查询、排序、分组、分页太常见了,所以 MySQL 默认索引结构选择了 B+ 树这一套(InnoDB 的 BTREE 索引)。


4. 为什么是 B+ 树(不是二叉树、不是红黑树)

一个非常"数据库味"的点:IO 才是瓶颈

内存里比较 100 次都不算啥,磁盘 IO 多来几次你就能看到慢查询了。所以索引结构的核心目标之一是:降低树高,减少 IO 次数

  • 二叉搜索树:最坏情况下退化成链表(O(N))
  • AVL/红黑树:能平衡,但毕竟是二叉结构,树高仍然不够友好
  • N 叉树:能有效降低树高
  • B+ 树:更适合磁盘/页结构,叶子节点有序链表,范围查询更舒服

B+ 树的特点:

  • 非叶子节点只做索引,不存真实数据
  • 真实数据都在叶子节点(或者叶子节点指向真实数据)
  • 叶子节点通过双向链表串起来,天然支持范围扫描

5. MySQL 的"页"(Page):16KB 的世界观

索引这块你只记住一句话就够用:页是内存与磁盘交互的最小单元,默认 16KB。

sql 复制代码
SHOW VARIABLES LIKE 'innodb_page_size';

你会看到默认是 16384(16KB)。

为什么要按页读?

  • 一次 IO 直接读一页,把邻近的数据也顺带读上来
  • 局部性原理:你刚访问过的数据,接下来大概率还会访问附近的数据

这也是 B+ 树为什么在数据库里好用:它的节点大小可以和"页"很好地对齐。


6. InnoDB 的两类核心索引:聚簇索引 & 二级索引

6.1 聚簇索引(Clustered Index)

在 InnoDB 里:

  • 有主键(PRIMARY KEY):主键就是聚簇索引
  • 没主键:会找第一个 UNIQUE + NOT NULL 的列当聚簇索引
  • 还没有:InnoDB 会生成一个 6 字节的 ROW_ID 来当聚簇索引

聚簇索引的直觉:叶子节点里存的就是整行数据

6.2 二级索引(Secondary Index)

聚簇索引以外的索引,都是二级索引。二级索引的叶子节点里一般存的是:

  • 索引列的值
  • 以及对应行的主键值

当你通过二级索引定位到主键后,还要回到聚簇索引里把整行数据捞出来,这个过程就叫:回表

6.3 索引覆盖(Covering Index)

如果你的查询只用到了某个二级索引里已经包含的列,那它就可以"不回表"直接返回结果,这种情况叫索引覆盖。

举个很实用的例子:

sql 复制代码
CREATE TABLE IF NOT EXISTS t_user (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  sno VARCHAR(10) NOT NULL,
  class_id BIGINT NOT NULL,
  name VARCHAR(20) NOT NULL,
  created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
  UNIQUE KEY uk_sno(sno),
  KEY idx_sno_class(sno, class_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

走覆盖索引(只查索引里的列):

sql 复制代码
SELECT sno, class_id
FROM t_user
WHERE sno = '100001';

容易回表(查询列里有 name,不在 idx_sno_class 的索引列里):

sql 复制代码
SELECT sno, class_id, name
FROM t_user
WHERE sno = '100001';

覆盖索引不是魔法,它的本质就是:你要的列,索引里刚好都有。


7. 索引分类

7.1 主键索引(PRIMARY KEY)

你给表加主键,InnoDB 会用它当聚簇索引:

sql 复制代码
CREATE TABLE t_pk (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(20)
);

7.2 普通索引(INDEX / KEY)

就是最基础的索引,不保证唯一性:

sql 复制代码
CREATE TABLE t_idx (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  sno VARCHAR(10),
  KEY idx_sno(sno)
);

7.3 唯一索引(UNIQUE)

唯一约束会自动创建唯一索引:

sql 复制代码
CREATE TABLE t_uk (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  sno VARCHAR(10) UNIQUE
);

7.4 全文索引(FULLTEXT)

用于文本检索(InnoDB/MyISAM 都支持,但适用场景更偏"文本搜索")。这一章先知道它存在就行,后面如果你做博客/文章检索再展开。


8. 索引怎么用:自动创建、手动创建、查看、删除

8.1 哪些情况会自动创建索引

三类特别典型:

  • 主键(PRIMARY KEY)
  • 唯一约束(UNIQUE)
  • 外键(FOREIGN KEY)

直觉也很简单:这些约束要么需要快速判断"是否冲突",要么需要快速检查"引用是否存在",没有索引基本就跑不动。

8.2 手动创建索引(几种方式)

建表时直接指定:

sql 复制代码
CREATE TABLE t_test_index6 (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(20),
  sno VARCHAR(10),
  class_id BIGINT,
  KEY idx_sno_class(sno, class_id)
);

建表后再加:

sql 复制代码
ALTER TABLE t_test_index6 ADD INDEX idx_sno(sno);
ALTER TABLE t_test_index6 ADD UNIQUE uk_name(name);

或者单独建索引:

sql 复制代码
CREATE INDEX idx_class_id ON t_test_index6(class_id);

8.3 查看索引

sql 复制代码
SHOW INDEX FROM t_test_index6;

你看索引时我建议重点关注这几个字段:

  • Key_name:索引名
  • Non_unique:0 表示唯一索引
  • Seq_in_index:复合索引里字段顺序(很关键)
  • Index_type:一般是 BTREE

8.4 删除索引(别把自己绕进去)

删除普通索引:

sql 复制代码
ALTER TABLE t_test_index6 DROP INDEX idx_class_id;

删除主键索引(课件里也专门提醒了一个坑):

如果主键列还是自增列,直接删主键会报错,因为自增列必须是 key。

正确姿势是先去掉自增属性,再删主键:

sql 复制代码
ALTER TABLE t_test_index6 MODIFY id BIGINT;
ALTER TABLE t_test_index6 DROP PRIMARY KEY;

9. 复合索引:最左前缀

复合索引的语法很简单,就是多个列用逗号隔开:

sql 复制代码
CREATE INDEX idx_a_b ON t_user(sno, class_id);

真正难的是怎么用:复合索引满足"最左前缀"。

也就是说,对于 (sno, class_id)

  • 能用索引:WHERE sno = ?
  • 能用索引:WHERE sno = ? AND class_id = ?
  • 很可能用不上:WHERE class_id = ?

10. EXPLAIN:你判断"到底走没走索引"的方式

索引相关的问题,最怕两句话:

  • "我明明建了索引,怎么还是慢?"
  • "我看着像走了索引,但我也不确定。"

别猜,直接 EXPLAIN

sql 复制代码
EXPLAIN
SELECT *
FROM t_user
WHERE sno = '100001';

你重点看这几个字段就够用:

  • type:访问类型(越像 index/range/ref 越好,ALL 通常就是全表扫)
  • possible_keys:可能会用的索引
  • key:实际用的索引
  • rows:预估扫描行数(越小越好)
  • Extra:有没有 Using index(常见于覆盖索引)

11. 一些非常常见的"索引失效写法"

11.1 对索引列做函数/运算

比如对时间列做函数:

sql 复制代码
WHERE DATE(created_at) = '2026-09-04'

很多时候会让索引不太好用。更常见写法是改成范围:

sql 复制代码
WHERE created_at >= '2026-09-04 00:00:00'
  AND created_at <  '2026-09-05 00:00:00'

11.2 LIKE 左边带 %

sql 复制代码
WHERE name LIKE '%三'

这种写法很难利用普通 BTREE 索引(因为前缀都不确定了)。

11.3 复合索引跳过最左列

你建了 (sno, class_id),但你只按 class_id 查:

sql 复制代码
WHERE class_id = 1

那就别指望它一定能走到这个复合索引。


本章小结

索引这章大概讲述了下面的内容:

  • 索引是为了减少扫描范围,代价是空间和写入维护成本
  • InnoDB 默认用 B+ 树,是为了减少 IO、支持范围查询、支持顺序遍历
  • 二级索引可能要回表,覆盖索引能省掉回表;复合索引要遵守最左前缀

下一章我们还会为大家介绍数据库另一个更加核心的要点:事务。也是在数据库当中属于核心的章节,喜欢的朋友记得给个三连呦~

相关推荐
博、、1 小时前
全民健身解决方案系统源码实战指南:从架构设计到部署全流程解析
开发语言·需求分析
liuze4081 小时前
安装boss-zhipin-mcp(招聘)
开发语言·python
博、、1 小时前
智慧场馆解决方案系统开发实战:从架构设计到落地指南
开发语言·需求分析
二十雨辰1 小时前
[Java]-场景题
java·开发语言
zcmodeltech2 小时前
智慧矿山沙盘模型多场景控制系统设计——基于STM32与Modbus RTU的智能开采、无人矿卡、数字孪生全场景联动方案
数据库·stm32·单片机·嵌入式硬件·物联网
小码过河.2 小时前
ocean自增主键模式说明
数据库·oracle
geovindu2 小时前
java:Observer Pattern
java·开发语言·后端·观察者模式·设计模式·行为模式