一、索引基础:索引是什么?它到底有什么用?

说实话,刚入行那两年,我一直把索引当“数据库的目录”。想象一下,一本上千页的书,没有目录的话,你想找“B+树”这个词,只能一页一页翻。索引干的就是类似的事——帮你跳过大量无关数据,直接定位到目标。

1.1 索引的本质

索引是一种独立于表数据的辅助结构。它存储了“键值 → 行位置”的映射关系。我个人习惯把索引想象成一套排好序的指针集合。排序是关键——只有排好序,才能用二分查找之类的高效算法。

核心公式:
全表扫描 = O(n),数据量翻倍,时间翻倍。
有索引查询 = O(log n),数据量翻倍,时间只增加一个常数。

1.2 索引主要解决了哪些问题?

我总结过三个核心场景,你品品:

  • 快速查找:WHERE条件命中索引,直接从B+树根节点走到叶子,拿到rowid回表。
  • 排序加速:ORDER BY字段是索引前缀时,MySQL直接按索引顺序读,省掉filesort。之前做过一个报表系统,把ORDER BY字段加到索引里,查询从8秒压到0.3秒。
  • 唯一性约束:主键索引、唯一索引保证数据不重复。没有索引的话,每次插入都要全表扫描判断,性能灾难。

我个人的经验:索引不是越多越好。每多建一个索引,写入数据时就要多维护一棵B+树。我曾经接手过一个表,有12个索引,导入100万行数据花了40多分钟……删掉4个冗余索引后,时间降到9分钟。

1.3 索引的分类

不同数据库、不同引擎的实现差别挺大。这里以最常见的InnoDB为例,我习惯把索引分成三大类:

分类维度 类型 特点
数据结构 B+树索引 默认、支持范围查询、OLTP场景首选
数据结构 哈希索引 Memory引擎默认,等值查询超快,不支持范围
数据结构 全文索引 用于LIKE '%word%',MyISAM和InnoDB5.6+支持
数据结构 R树/空间索引 MyISAM支持,地理坐标查询用
存储方式 聚簇索引 InnoDB主键就是聚簇索引,叶子节点存整行数据
存储方式 非聚簇索引(二级索引) 叶子节点存主键值,需要回表
约束 主键索引 唯一且非空,InnoDB自动聚簇
约束 唯一索引 允许NULL,可以有多个
约束 普通索引 单纯加速,不约束唯一
列数 单列索引 只包含一个字段
列数 联合索引 多个字段,最左前缀原则

嗯,这张表其实藏着不少坑。比如联合索引的最左前缀原则——我见过太多人建了(A,B,C)联合索引,然后只查B列,心里还纳闷为什么没走索引。为什么会这样?因为MySQL只能从索引最左边开始匹配,跳过A直接查B,相当于没索引。

避坑指南:我曾经在“用户登录日志”表上建了(user_id, login_time)联合索引。业务方经常查“某天登录的所有用户”,传参是login_time BETWEEN…,没有user_id。结果每次全表扫描。后来改成(login_time, user_id)才解决问题。

1.4 一张图说清索引的体系

下面这张图我花了不少心思,把索引分类逻辑和它的“灵魂”——B+树结构——放在一起。你品一品:

索引分类体系 索引(Index) 按数据结构 按存储方式 按约束 按列数 B+树索引 哈希索引 全文索引 空间索引 聚簇索引 非聚簇索引 主键索引 唯一索引 普通索引 单列索引 联合索引 💡 实际业务中,B+树索引(尤其是聚簇索引)占据了90%以上的场景。 哈希索引只在Memory引擎或自适应哈希索引中“偷偷”用。

看图你会发现,索引分类的维度是正交的。一个索引可以同时属于多个门类:比如InnoDB的主键索引,它既是B+树索引,又是聚簇索引,也是主键索引,还是单列索引。理解这个交叉关系,后面讲组合索引设计时你就能明白为什么有些规则是硬性的。

1.5 一个具体例子:创建索引的SQL

写代码时最常用的是这几条:

-- 单列普通索引
CREATE INDEX idx_user_name ON users(name);

-- 联合索引
CREATE INDEX idx_name_age ON users(name, age);

-- 唯一索引
CREATE UNIQUE INDEX idx_email ON users(email);

-- 全文索引(InnoDB 5.6+)
CREATE FULLTEXT INDEX idx_content ON articles(content);

-- 删除索引
ALTER TABLE users DROP INDEX idx_user_name;

你想想看,第二条idx_name_age,如果查询条件是WHERE age = 20,它会不会走索引?不会。最左前缀——name必须出现。但反过来,WHERE name = '张三'会走索引吗?当然会,因为name在最左边。嗯,这个坑我刚开始也踩过。

小技巧:我习惯在开发阶段用EXPLAIN看一眼执行计划,确认索引被用上了。比如:

EXPLAIN SELECT * FROM users WHERE name = '张三';

输出里type列如果是ref或range,说明索引生效。如果看到All(全表扫描),那就去优化WHERE条件或者建索引吧。

1.6 索引不是银弹——存储成本

前面一直在说好处,但我必须提醒你:索引是要吃磁盘空间的。拿InnoDB举例,每个二级索引的B+树都会占用一份存储。我碰到过一个极端案例:某日志表数据量300GB,索引占了500GB!为什么?因为建了6个联合索引,每个索引的叶子节点都存了主键(8字节)和索引字段,加上页内碎片,累积下来比数据本身还大。

所以从这一章开始,我们就埋下一根主线:索引带来的查询加速 vs 存储成本之间的平衡。后面每一章都会围绕这个平衡展开——怎么选字段、怎么建组合索引、怎么用覆盖索引省掉回表、怎么在OLAP场景里用倒排索引……你跟着我走一遍,到时候自己做决策心里就有数了。


(第1章完)