理解 MySQL 索引,关键不是背诵“B+ 树查询快”,而是看清 InnoDB 要解决的约束:数据规模远大于内存、存储按页读写、业务既有等值查询也有范围扫描,同时还要承受持续更新。B+ 树并非所有数据库场景的唯一答案,却很适合 InnoDB 的通用事务负载。
先澄清:不是所有 MySQL 索引都只有 B+ 树
讨论前要限定范围。InnoDB 的普通索引以 B-tree 结构组织,工程语境通常称为 B+ 树;空间索引使用 R-tree,全文索引也有不同的组织方式。其他存储引擎还可能提供 Hash 索引。
因此,更准确的问题是:为什么 InnoDB 的聚簇索引和普通二级索引采用适合页式存储的多路有序树,而不是二叉树或纯 Hash?
答案与三个目标有关:
- 降低定位记录时需要访问的页面数量;
- 同时支持等值、范围、排序和前缀匹配;
- 在查询性能、更新成本和空间占用之间取得平衡。
为什么“宽而矮”适合页式存储
InnoDB 索引由页面组成,默认页面大小是 16KB,但实例初始化时也可以配置其他受支持的页大小。一次读取会带入整个页面,而不是只读取一个键值。
二叉搜索树的每个节点分支少。即使维持平衡,数据量增大后树层数仍会较高;若把节点分散到不同页面,沿树向下查找就可能访问更多页面。B+ 树的非叶子节点主要保存用于导航的键和子页指针,一个页面能够容纳较多分支,因而通常可以用较少层级覆盖大量记录。
这里不宜套用“查一次必定发生几次磁盘 IO”的口诀。Buffer Pool 会缓存索引页,根页和热点上层页往往能够直接从内存命中。树高仍然重要,但实际延迟还受缓存命中率、存储介质、并发访问和查询返回行数影响。
有序叶子层为什么比纯 Hash 更通用
Hash 结构擅长按完整键做等值查找,但天然不保留键的顺序。常见业务不只有 id = ?,还包括:
WHERE created_at >= ? AND created_at < ?
ORDER BY created_at
LIMIT 50有序树可以先定位范围起点,再沿叶子层继续扫描。相同的顺序性也能服务于部分排序、分组和最左前缀查询。纯 Hash 难以直接承担这些任务,因此更适合作为特定访问路径,而不是 InnoDB 通用索引的基础结构。
与 B-tree 的理论变体相比,把完整行或索引记录集中在叶子层、让非叶子层更专注于导航,可以提高内部节点的扇出,并让范围扫描更连贯。这正是 B+ 树思路与数据库页面模型契合的地方。
聚簇索引与二级索引如何配合
每张 InnoDB 表都有一个聚簇索引,叶子记录包含行数据。通常,显式定义的主键会成为聚簇索引;如果没有合适的主键,InnoDB 会按其规则选择或生成聚簇键。
二级索引的叶子记录保存二级索引列以及对应的主键值,而不是固定的物理行地址。查询若需要二级索引中没有的列,通常先通过二级索引取得主键,再访问聚簇索引,这就是常说的“回表”。
这套设计带来几个直接的选型结论:
- 主键不宜过宽。 主键值会进入各个二级索引,宽主键会放大索引空间与缓存压力。
- 主键应尽量稳定。 修改聚簇键会牵涉记录组织与相关索引维护。
- 覆盖索引有机会减少回表。 如果查询所需列都能从二级索引取得,就可能省去再次访问聚簇索引的步骤。
- 索引不是越多越好。 每个新增索引都会占空间,并增加插入、更新和删除的维护成本。
是否采用自增主键不能只靠一句口诀决定。顺序增长的窄主键通常有利于减少随机插入带来的页面整理,但分布式发号、业务唯一性、写入热点和迁移需求也要一起评估。
联合索引要围绕访问路径设计
假设建立联合索引:
INDEX idx_tenant_status_time (tenant_id, status, created_at)优化器可以利用它的最左前缀,例如 (tenant_id)、(tenant_id, status),以及完整的三列组合;只按 status 或 created_at 查找,则不能把它们当作该索引的左侧查找前缀。
设计列顺序时,可按以下步骤判断:
- 先列出高频查询真实使用的过滤、连接和排序条件;
- 优先保证最常见访问路径,而不是机械地把“区分度最高”的列放最前;
- 观察范围条件之后的列是否还能参与索引查找或索引条件下推;
- 用
EXPLAIN与真实数据分布验证,避免只根据 SQL 外观下结论。
需要特别注意,“建立了索引”不等于优化器必然选择它。如果条件会命中表中很大比例的数据,或者回表成本过高,全表扫描可能反而更便宜。统计信息变化后,同一条 SQL 的执行计划也可能变化。
从 B+ 树原理落到慢查询排查
排查索引问题时,可以按下面的顺序进行:
- 用
EXPLAIN查看访问类型、候选索引、实际选择的索引和预估行数; - 检查比较两侧的数据类型、字符集与排序规则是否匹配;
- 检查联合索引是否满足所需的最左前缀;
- 判断函数、表达式或前导通配符是否改变了可直接查找的键;
- 评估返回比例与回表次数,而不是只看“是否走索引”;
- 对深分页考虑基于稳定排序键的游标式翻页,而不是不断增大
OFFSET; - 在接近生产的数据量和分布上压测,并持续观察慢查询与执行计划。
怎么判断是否值得新增索引
对个人站长和小团队而言,索引设计应从业务查询出发,而不是从表字段出发。新增索引前至少回答四个问题:
- 它服务的是高频查询,还是偶发后台任务?
- 能否与现有联合索引合并,避免重复索引?
- 节省的读取成本是否大于写入和存储成本?
- 数据增长后,选择性与查询模式是否仍然成立?
B+ 树的价值,在于用较低树高和有序叶子层兼顾点查与范围访问;但最终性能仍由索引列顺序、数据分布、缓存、返回行数和 SQL 写法共同决定。掌握这些约束,比记住“索引失效口诀”更能指导实际优化。