mysql索引结构&&为什么不用哈希作为主力索引

MySQL 索引的结构是什么?为什么不用哈希表?

一、MySQL(InnoDB)索引的核心结构:B+Tree

在 InnoDB 存储引擎中,绝大多数索引(主键索引、唯一索引、普通索引、联合索引)的底层结构都是 B+Tree

1. B+Tree 是什么?

B+Tree 是一种“多叉平衡树”,特点:

  • 每个节点可以存放大量 key + 子指针
  • 树高度一般很低(2~4 层),查找复杂度为 O(logN)
  • 所有数据都在叶子节点,中间节点只存 key;
  • 叶子节点之间通过双向链表相连,天然有序。

简单理解一次查询过程:

  1. 从根节点开始,根据 key 范围选择合适的子指针;
  2. 层层向下,直到叶子节点;
  3. 在叶子节点中找到满足条件的记录(或范围起点);
  4. 如需范围查询,就沿着叶子链表向后扫描。

2. 聚簇索引与二级索引

InnoDB 有两个关键概念:

2.1 聚簇索引(Clustered Index)
  • 主键索引就是聚簇索引
  • B+Tree 的叶子节点存的不是“指针 + 行地址”,而是整行数据
  • 表数据按照主键顺序存放,逻辑上相当于一棵“以主键为 key 的 B+Tree”。
2.2 二级索引(Secondary Index)
  • 普通索引、唯一索引等都是二级索引;
  • 叶子节点存的是:索引列值 + 主键值,而不是整行;
  • 查询流程:
    1. 在二级索引上根据索引列查到主键;
    2. 再回到聚簇索引上按主键查整行数据(“回表”)。

B+Tree 的这一设计决定了:

  • 索引天然是“有序”的
  • 查询、排序、范围扫描可以复用同一个结构

二、为什么不用哈希表做主力索引?

很多人会想:哈希表查找是 O(1),看起来比 O(logN) 更快,那为什么不用哈希表?
关键在于:数据库的查询需求远不止“根据主键等值查询”这么简单

下面按几个维度拆开说。

1. 范围查询是刚需,哈希天生不擅长

日常非常常见的 SQL:

SELECT * FROM user
WHERE age BETWEEN 20 AND 30;

SELECT * FROM order
WHERE create_time >= '2025-01-01'
  AND create_time < '2025-02-01';

SELECT * FROM product
WHERE price < 100;
  • B+Tree 的做法

    1. 找到 age >= 20 的最左记录;
    2. 沿着叶子节点的链表顺序扫,直到 age > 30 停止;
    3. 中间是“局部顺序 I/O”,非常友好。
  • 哈希表的问题

    • 哈希之后 key 被打散到不同的桶中;
    • “20~30 这一段”完全找不到连续区域;
    • 若想找范围内的所有 key,等价于遍历哈希表中的大量桶 —— 性能接近全表扫描。

结论:对于范围查询这种数据库高频场景,哈希表几乎帮不上忙。


2. 排序 / 分组 / 前缀匹配都需要“有序性”

例如:

-- 排序
SELECT *
FROM order
WHERE user_id = 100
ORDER BY create_time DESC
LIMIT 20;

-- 分组
SELECT user_id, COUNT(*)
FROM order
GROUP BY user_id;

-- 前缀匹配
SELECT *
FROM user
WHERE name LIKE 'zhang%';
  • B+Tree 的优势

    • key 在树中是有序的
    • ORDER BY 可以直接利用索引的顺序,避免额外排序(filesort);
    • GROUP BY 可以在有序数据上顺序扫,同一 group 的 key 会聚集在一起;
    • LIKE 'abc%' 本质是一个“以 'abc' 为起点的范围查询”,也能用 B+Tree 做到。
  • 哈希表的劣势

    • key 被 hash 成随机分布,完全没有顺序可言
    • 想排序必须把结果拎出来再排序;
    • 想分组也必须扫一遍再分桶;
    • LIKE 'abc%' 无法利用 hash 值进行范围定位,只能遍历匹配。

数据库是一个需要大量复杂查询(排序、分组、范围、前缀)的系统,
哈希表只能在“单 key 等值查询”这一个点上亮眼,而 B+Tree 则是综合能力强


3. 联合索引与最左前缀需求,哈希搞不定

对于联合索引:

CREATE INDEX idx_user_status_time
ON t_order (user_id, status, create_time);

本质顺序是:

  1. 先按 user_id 排序;
  2. 同一个 user_id 内再按 status 排序;
  3. 再按 create_time 排序。

所以以下条件都能很好利用这个索引:

WHERE user_id = ?;

WHERE user_id = ? AND status = ?;

WHERE user_id = ?
  AND status = ?
  AND create_time > ?;

这就是最左前缀原则

如果用哈希表实现联合索引,一般会把多个列拼起来算一个哈希:

hash(user_id, status, create_time)

问题就来了:

  • 查询 WHERE user_id = ? 时:没有办法只用 user_id 这个前缀来利用哈希;
  • 查询 WHERE user_id = ? AND status = ? 时也类似;
  • 联合 key 一旦被 hash 成一个值,前缀信息就被“揉碎”了

而在实际业务里,“以某个字段为前缀进行查询”的需求太普遍了,
B+Tree 能天然支持,哈希表则不行。


4. 磁盘 I/O 模型下,B+Tree 更适合大数据量

数据库面对的不是仅内存环境,而是磁盘 + 大数据量

  • B+Tree 的磁盘表现

    • 每个节点对应一个(或少数几个)磁盘页,例如 16KB;
    • 一个节点可以容纳很多 key,分叉多,树高度低;
    • 查找一条记录只需要访问极少的页(几次随机 I/O);
    • 范围扫描是沿着叶子节点链表顺序读,一般是较好的顺序 I/O。
  • 哈希表的磁盘表现

    • 桶可能分布在大量离散的磁盘页上;
    • 范围查询/遍历桶会产生大量随机 I/O;
    • 哈希扩容、rehash 在磁盘环境下代价极大。

磁盘 I/O 是主要瓶颈的大数据场景中,B+Tree 的结构明显更适合做通用索引。


5. 哈希本身也有问题:冲突和扩容

  • 哈希冲突

    • 不同的 key 可能落到同一个桶;
    • 需要通过链表、开放寻址等方式解决;
    • 冲突增多时,性能会退化,最坏几乎退化为 O(N)。
  • 扩容问题

    • 当哈希表装满到一定比例,就需要扩容;
    • 扩容意味着整表 rehash,把所有 key 重新分布到新的桶中;
    • 对于大规模持久化数据来说,这个成本非常高。

相比之下,B+Tree 的页分裂、页合并是局部操作,开销相对温和、渐进,整体行为更稳定。


三、MySQL 其实也在用哈希,只是用得很克制

并不是 MySQL 完全不用哈希,而是各做各的事

1. MEMORY 引擎的 HASH 索引

CREATE TABLE demo (
    id INT,
    name VARCHAR(20),
    PRIMARY KEY (id)
) ENGINE = MEMORY;
  • MEMORY 引擎默认就支持 HASH 索引;
  • 表数据放在内存中,适合做临时表或缓存;
  • 主打一个“等值查询快”,并不适合复杂查询。

2. InnoDB 的自适应哈希索引(Adaptive Hash Index)

InnoDB 内部会:

  1. 监控哪些 B+Tree 页面被频繁访问;
  2. 对这些“热点范围”再建立一层哈希索引;
  3. 让部分等值查询可以直接通过哈希命中,而不用每次都爬 B+Tree。

注意:

  • 这只是对 B+Tree 的一个加速层
  • 主体结构仍然是 B+Tree;
  • 是否开启/如何实现由引擎自己管理,我们不需要手动构建。

四、小结

可以用几句话直接概括:

  1. MySQL(InnoDB)的索引核心是 B+Tree,不是哈希表。
  2. 使用 B+Tree 的关键原因:
    • 支持 范围查询
    • 支持 排序 / 分组 / 前缀匹配 等有序需求;
    • 支持 联合索引 + 最左前缀 等复杂索引设计;
    • 更适合 磁盘 I/O 模型,树高低、范围扫描友好;
    • 行为稳定,不存在大规模 rehash 的问题。
  3. 哈希表只在 MySQL 里扮演“配角”:
    • MEMORY 引擎的 hash 索引适合等值查询;
    • InnoDB 的自适应哈希索引用来加速热点查询。

一句话总结:

数据库不是简单的“key → value”存取系统,而是需要大量范围、排序、分组等操作的复杂系统。
B+Tree 提供的是通用、稳定、综合能力很强的索引能力,哈希则更适合作为局部加速手段,而不是整个系统的基础索引结构。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值