一、什么是索引
索引是数据库中对表中一列或多列的值进行排序的一种数据结构。它类似于一本书的目录,通过建立「键值 → 数据位置」的映射关系,帮助数据库快速定位目标数据,而不必逐行扫描整张表。
简单来说,索引是以存储空间换取查询时间的极大缩短。
二、索引的作用
- 加快数据检索的速度:通过减少查询过程中需要扫描的数据行数,将全表扫描(O(n))优化为近似 O(log n) 的树查找。
- 加快表连接和排序:索引本身是有序的,数据库可以直接利用索引的有序性完成排序,避免额外的文件排序(Filesort)操作。
- 保证数据的唯一性:通过创建唯一索引(UNIQUE),数据库可以强制保证某列(或多列)数据的唯一性。
三、索引的代价(缺点)
- 占用存储空间:每个索引都需要额外的磁盘空间来存储索引结构。
- 降低写入性能:对表进行 INSERT、UPDATE、DELETE 时,除了修改数据本身,还需要同步维护所有相关索引,写操作成本上升。
- 可能失效或误用:索引使用不当(如违反最左前缀、字段类型不匹配等)不仅无法加速,反而可能拖慢查询。
因此索引并非越多越好,需要根据实际查询场景权衡。
四、索引的原理
B+树(B+树是 B 树的一种变种)
- 分层的节点结构:B+树由根节点、内部节点和叶子节点构成。
- 内部节点:只记录存储索引键值和指向子节点的指针,不存储实际数据。
- 叶子节点:所有的数据都存储在叶子节点上,这些叶子节点通过双向链表连接,形成了一个有序的数据序列。
- 树高稳定:所有叶子节点都在同一层,这意味着查找任何数据都需要经历相同层数的路径,查询时间非常稳定。
为什么使用 B+ 树
- 大幅减少磁盘 I/O:磁盘 I/O 是数据库操作中最慢的环节。B+树通过其「矮胖」的结构,将查找数据所需的磁盘读取次数降到最低(通常 2-3 次)。每个节点的大小通常被设计为等于一个磁盘页(如 MySQL 的 16KB),使得一次 I/O 就能读取一个完整的节点。相比之下,二叉树等结构在数据量大时树高会急剧增加,导致 I/O 次数剧增。
- 卓越的范围查询能力:数据库的范围查询(如 BETWEEN、>、<)非常频繁。B+树的叶子节点通过链表串联,一旦定位到范围的起点,只需沿着链表顺序扫描即可,效率极高。而 B 树的数据分散在各处,进行范围查询需要进行复杂的中序遍历,效率低下。
- 查询性能稳定:在 B+树中,任何查找都必须从根节点走到叶子节点,因此所有查询的路径长度都相同,查询效率非常稳定。而在 B 树中,数据可能存储在任意节点,数据靠近根节点时查询快,靠近叶子节点时查询慢,性能不稳定。
- 更高的空间利用率:由于内部节点不存数据,可以存储更多的键值,这进一步降低了树的高度,也使得数据在磁盘上存储得更紧密,具有良好的空间局部性,能提高缓存命中率。
五、索引的分类
按物理存储方式(以 MySQL InnoDB 为例)
| 类型 | 说明 | 特点 |
聚集索引 Clustered |
索引的叶子节点直接存储整行数据(数据即索引),主键索引就是聚集索引。 |
每张表只能有一个。 |
非聚集索引 Secondary |
叶子节点存储的是主键值(而非数据本身),需要通过主键再查一次数据。 |
可以有多个,查询时可能发生「回表」。 |
- 回表:通过非聚集索引查询到主键后,再用主键去聚集索引中查找完整数据行的过程。
- 覆盖索引:查询所需的列全部包含在索引中时,无需回表,直接返回结果,性能最高。
按功能 / 约束分类
| 类型 | 说明 |
| 主键索引(Primary Key) | 唯一且非空,InnoDB 中即聚集索引。 |
| 唯一索引(Unique) | 保证列值唯一,允许 NULL。 |
| 普通索引(Normal) | 最基本的索引,无任何约束。 |
| 联合索引(Composite) | 由多个列组合成的索引,遵循最左前缀原则。 |
| 全文索引(Fulltext) | 用于大文本的全文检索。 |
| 前缀索引 | 对字符串的前 N 个字符建立索引,节省空间。 |
六、联合索引与最左前缀原则
联合索引由多列组成,例如 INDEX(a, b, c),内部排序规则是:先按 a 排序,a 相同再按 b 排序,b 相同再按 c 排序。
最左前缀原则:查询条件必须从联合索引的最左侧列开始,且不能跳过中间列,否则无法完全利用该索引。
例如对 INDEX(a, b, c):
- ✅ 能走索引:
where a = ?、where a = ? and b = ?、where a = ? and b = ? and c = ?
- ❌ 不能走索引:
where b = ?、where c = ?、where a = ? and c = ?(跳过 b,只能用到 a 这一列)
七、索引失效的常见场景
- 对索引列使用函数或表达式运算,如
where year(create_time) = 2025
- 隐式类型转换,如字符串列与数字比较
where phone = 13800000000
- 使用 like 以
% 开头,如 where name like '%张三'
- 联合索引违反最左前缀原则
- 查询条件使用 OR 且存在非索引列
- 使用
!=、<>、not in 等可能不走索引
- 索引列参与计算,如
where age + 1 = 20
八、建索引的建议
- 为 WHERE、ORDER BY、GROUP BY、JOIN 中频繁使用的列建索引。
- 区分度高(选择性好)的列适合建索引,如主键、唯一字段。
- 联合索引要注意列的顺序,把区分度高、查询频繁的列放在最左。
- 避免在频繁更新的列上建过多索引。
- 尽量使用覆盖索引,减少回表。