索引基础:B+树、哈希、全文索引

我记得刚接触数据库时,觉得索引就是个黑盒子。

慢查询就加索引,加完快了就完事。

后来被线上事故狠狠教训过——选错索引类型,直接拖垮了整个库。

今天我就把这三种核心索引掰开揉碎讲清楚。你自己想想看,连底层都不摸清,调优就是瞎蒙。

B+树索引——绝大多数场景的默认选择

B+树,说白了就是个平衡的多路搜索树。

跟普通二叉树不同,B+树的节点可以存几百个键值。树的高度通常只有3-4层。这意味着什么?你查3-4次磁盘I/O就能找到任何一行数据。

一个常见误区:很多人以为聚簇索引和非聚簇索引是两种索引类型。其实它们都是B+树,只是叶子节点存的东西不一样。

特性 聚簇索引 非聚簇索引(二级索引)
叶子节点内容 整行数据 主键值 + 索引列
表组织方式 索引即数据 独立于数据
回表查询 不需要 需要二次查找
每表最多 1个 多个
个人习惯:在MySQL InnoDB中,我设计表时一定会选一个递增的整数主键。为什么?B+树插入有序键不会触发页分裂,写入性能稳定。

哈希索引——等值查询的闪电战

哈希索引的原理简单粗暴:把键值通过哈希函数映射到固定位置的桶里。

等值查询,O(1)复杂度。比B+树的O(log n)快了一个量级。

但缺点也很致命:

  • 不支持范围查询——哈希函数破坏了键的顺序性
  • 不支持部分匹配——比如like 'abc%'直接凉凉
  • 不支持排序——ORDER BY对哈希索引完全无效
  • 哈希冲突——大量冲突时性能骤降

我记得之前在项目里遇到过一个问题:业务方把某个自增ID字段建成了哈希索引。

等值查询确实快到飞起。

但后来加了分页排序功能,那个查询瞬间变成全表扫描。

嗯,这就是典型的索引类型选型失误。

核心结论:哈希索引只适合等值查询场景。如果你的表主要用来做KV查询(比如缓存类业务),哈希索引是好选择。否则,老老实实用B+树。

自适应哈希索引——MySQL的自动优化

InnoDB有一个隐藏的宝藏:自适应哈希索引(AHI)。

它会在运行时自动监控二级索引的访问模式。如果发现某些页频繁被等值查询命中,InnoDB会自动在内存中为这些页构建哈希索引。

这个过程完全无感。开发者不需要做任何配置。

但要注意:

  • AHI只存在于内存,重启后清空
  • 如果表上有大量无规律的等值查询,AHI可能会占用太多内存
  • 极端情况下,AHI管理锁争用反而会成为瓶颈
我踩过的一个坑:曾经一张大表的等值查询突然变慢,排查了半天,发现是AHI占满了InnoDB buffer pool,导致热点数据大量换入换出。最后不得不手动关闭了AHI。

全文索引——文本搜索的专属武器

全文索引跟B+树、哈希完全不同。它不是基于键值映射,而是基于倒排索引。

简单说,它的原理是:

  1. 把文本分词,拆成一个个单词
  2. 建立单词→文档ID的映射表
  3. 搜索时会匹配分词后的结果

为什么要单独搞一套?你用B+树做like '%keyword%'试试。走不了索引,全表扫描,慢得要命。

全文索引专门解决这个问题。

使用场景:

  • 文章内容的模糊搜索
  • 日志关键词匹配
  • 商品描述的快速检索

注意事项:

  • MySQL的全文索引只支持MyISAM和InnoDB(5.6+)
  • 中文分词依赖插件(ngram或jieba)
  • 全文索引维护成本高,不适合写频繁的表

三类索引总结对比

维度 B+树索引 哈希索引 全文索引
数据结构 平衡多路搜索树 哈希表 倒排索引
等值查询 O(log n) O(1) O(m),m为匹配文档数
范围查询 ✅ 支持 ❌ 不支持 ❌ 不支持
排序 ✅ 支持 ❌ 不支持 ❌ 不支持
模糊匹配 仅前缀匹配 不支持 分词匹配
磁盘占用 中 小 大
适用场景 通用场景,90%的查询 高并发等值查询 文本内容搜索
数据库索引分类与适用场景 数据库索引 B+树索引 聚簇索引 二级索引 范围查询 哈希索引 精确等值查询 无法排序/范围 全文索引 文本模糊搜索 内存消耗大 B+树适用90%场景,哈希索引用于高并发等值,全文索引专治文本搜索
选型口诀:
等值高频用哈希,
范围排序B+树,
文本搜索上全文,
选错类型后悔哭。

一个实战案例

之前我接手过一个订单系统。订单表有500万数据,经常需要查某个用户最近30天的订单。

原始SQL:

SELECT * FROM orders 
WHERE user_id = 12345 
AND create_time BETWEEN '2024-01-01' AND '2024-01-31'
ORDER BY create_time DESC;

原先是单列索引在user_id上。每次查询都要过滤大量时间数据,回表次数爆炸。

我的优化方案:

  1. 改成联合索引:(user_id, create_time)
  2. 利用B+树的顺序性——索引内时间已经是排序好的
  3. 避免回表——如果只查索引列,可以走覆盖索引

改完之后,查询时间从1.2秒降到了8毫秒。嗯,你没看错,就是这么夸张。

这就是我为什么一直强调:索引类型选对只是第一步,真正的高手,会把索引结构和查询模式匹配到极致。