MySQL (InnoDB)主要使用 B+ 树索引,而不是哈希、二叉树或B树。

MySQL InnoDB B+树

使用B+树的主要原因是B+树对磁盘IO友好,减少了磁盘IO操作,从而提高了查询的速度。

想象你在一本1000页的书中查找某个知识点,如果没有目录(索引),你只能一页一页的翻,这其实就是全书查找(对比到数据库就是全表扫描),这样的查找方式非常慢(效率低下)。但如果这本书有详细的目录那查找起来是不是快了很多?而B+树不仅是有目录,还是多层级目录(根目录只存大概分类、第2层目录存细化分类、。。。、最终数据-叶子节点),这样查询效率自然就高了。

B+树的非叶子节点只存储键值,不存储数据,叶子节点存数据;另外B+树是多叉树(相对二叉树),通常一个节点包含多个子节点,这就减少了树的高度。

举个栗子🌰:如果一个B+树索引有1000个分叉,那差不多只需要3层即可存储10亿条数据,这意味着查询时最多只需要3次磁盘IO。

注意:实际应用中索引键的类型和行数据的大小将直接影响数据的存储量

// InnoDB存储引擎每个节点(页)的大小通常为16KB
// 每个索引项由索引键值和指向子节点的指针组成
// 假设键值以 bigint 类型为例,需占用 8 字节,指向子节点的指针占用固定的 6 字节
// 每个索引项的大小 = 8 + 6 = 14字节
每个节点可以存储的索引项数量 = 16KB / (8B + 6B) = 1170 

// 三层 B+ 树的存储容量计算方法如下:
第一层根节点):存储 1170 个指向第二层的指针
第二层中间节点):每个节点再存储 1170 个指针 = 1170 x 1170 = 1368900 个叶子节点
第三层叶子节点):存储实际的数据行指针

假设每行数据大小为1KB则每个叶子节点16KB可以存储16行数据那么三层 B+ 树的
总存储容量 = 1368900叶子节点数x 16每个叶子节点存储的数据行数= 21902400 
也就是大约两千一百万行数据

所以优化MySQL性能的一个可行方案就是控制索引键和要存储数据的大小,如下表,索引键占用的空间越大每个索引页能存储的索引项就越少,从而可能导致B+树的层级增加,以至查询性能下降(磁盘IO次数变多)。

数据类型占用大小是否适合索引?备注
TINYINT1字节
SMALLINT2字节
INT4字节
BIGINT8字节
CHAR(10)10字节⚠️定长字符串,存储开销比INT大
VARCHAR(255)变长(最长767字节)变长字符串会导致索引存储空间大,影响性能
TEXT/BLOB不定长(存储在外部页)MySQL不能直接索引TEXT/BLOB类型

另外也可以通过启动MySQL时指定 –innodb-page-size=32K 或 通过my.cnf修改MySQL默认节点的大小的方式来降低树的高度:

[mysqld]
innodb_page_sie = 32K # 支持的页大小 4K、8K、16K、32K、64K

通过my.conf方式修改后还需重新初始化MySQL数据目录:

rm -rf /var/lib/mysql
mysqld --initialize

B+树对范围查询(BETWEEN、ORDER BY、GROUP BY等)友好:

叶子节点通过双向链表相连,支持高效的区间扫描。

B+树支持顺序查询:

由于叶子节点是有序链表,所以使用索引时可以直接进行顺序扫描,提高查询效率。

为什么 MySQL 的索引通常选择 B+ 树,而不是 B 树或哈希索引?

与B树对比,B树每个节点既存键,也存数据,这就会导致数据分布在整个树上,树的高度变大了磁盘IO的次数也就变大了,范围查询时需要回溯多个节点,效率较低。而B+树是叶子节点存储所有数据,并通过双向链表连接,范围查询性能更好。

与哈希索引对比,哈希索引适用于等值查询(如 WHERE id =100),但不适用于范围查询(如 WHERE id > 100);哈希索引无法利用有序性进行范围查询或排序,而B+树索引可以;另外,哈希索引冲突可能导致性能下降。