MySQL 索引怎么理解:B+ 树、回表、覆盖索引与最左匹配
MySQL 索引章节,讲清索引为什么能减少全表扫描,InnoDB 主键索引和二级索引有什么区别,以及回表、覆盖索引、联合索引、最左匹配和索引下推该怎么理解。
相关工具
索引先解决一个朴素问题
MySQL 索引不是为了让概念变复杂,它先解决一个很朴素的问题:表里数据很多时,怎样少找一点。文中把索引比作书的目录,这个比喻对初学者很友好。你要在一本厚书里找某个章节,不会从第一页翻到最后一页,而是先看目录,定位页码,再翻过去。
数据库也是一样。文中说,MySQL 查询主要有两种方式:全表扫描和根据索引检索。全表扫描就是一行一行找,数据量小时不明显,数据量大时会变成很重的磁盘和 CPU 消耗。索引检索则先沿着索引结构定位记录,尽量减少要读取的数据块。
所以理解索引,不要只记“索引能提高查询速度”。更准确地说,合适的索引能让 MySQL 少扫描行、少读数据页、少做无效比较。它不是免费加速器。索引本身也要占空间,写入、更新、删除时还要维护索引结构。读多写少、查询条件稳定的表,索引收益通常更明显;频繁写入且查询条件混乱的表,索引反而需要更谨慎。

主键索引叶子节点存整行数据;二级索引叶子节点存主键值,必要时要回到主键索引再查一次。
为什么不是普通二叉树
索引可以有很多数据结构。资料 按数据结构列了 B+Tree 索引、Hash 索引、Full-text 索引,也补充了哈希表、有序数组、二叉搜索树、N 叉树的特点。哈希表适合等值查询,但因为 key 无序,区间查询很慢;有序数组等值查询和范围查询都不错,但插入、删除时移动成本高;二叉搜索树查询效率高,但索引不只存在内存里,还要落到磁盘。
磁盘访问是理解数据库索引的关键。文中有一句话很重要:为了让一次查询尽量少读磁盘,就必须让查询过程访问尽量少的数据块。普通二叉树每个节点分叉少,数据量大时树会变高,访问路径变长,磁盘 I/O 次数也会变多。
N 叉树的思路是让一个节点保存更多索引项,降低树高。树矮了,定位记录时访问的数据块就少。InnoDB 使用 B+ 树索引模型,每一个索引在 InnoDB 里对应一棵 B+ 树,本质上就是在查询效率、范围查询能力和磁盘读取次数之间做了工程折中。
主键索引和二级索引不是一回事
按物理存储分类,资料 把索引分为聚簇索引,也就是主键索引,以及二级索引,也叫辅助索引。两者最大的区别在叶子节点上:主键索引的 B+ 树叶子节点存放实际数据,完整的用户记录就在叶子节点里;二级索引的 B+ 树叶子节点存放的是主键值,而不是整行数据。
这会直接影响查询路径。如果按主键查询,MySQL 在主键索引 B+ 树中找到叶子节点,就能拿到整行记录。比如 `where id = 1001`,如果 `id` 是主键,查到叶子节点后就结束了。
如果按普通索引查询,过程通常多一步。比如 `where phone = '138...'`,`phone` 上有普通索引,MySQL 先在 phone 对应的二级索引 B+ 树中找到主键值,再根据这个主键值回到主键索引里查整行记录。这个“再回主键索引查一次”的动作,就是常说的回表。资料 也明确提到,基于非主键索引的查询需要多扫描一棵索引树。
覆盖索引为什么能省一次回表
回表不是错误,只是成本。问题在于,如果一次查询需要回表很多次,性能就会被拖下来。覆盖索引就是为了减少这种成本。文中说,当查询的数据在二级索引的 B+ 树叶子节点里就能拿到时,就不用再查主键索引,这个过程叫覆盖索引。
比如有联合索引 `(user_id, status)`,查询语句是 `select user_id, status from order where user_id = 1001`。如果返回字段都在这个索引里,MySQL 在二级索引上就能拿到结果,不必回表查整行订单。反过来,如果写的是 `select *`,需要的字段超过了索引里已有内容,就很可能还要回表。
这也是为什么线上 SQL 优化时,很多人会先把 `select *` 改成只查必要字段。少查字段不只是减少网络传输,也可能让查询满足覆盖索引,少走一棵 B+ 树。索引优化不是单纯建索引,还要让 SQL 返回的字段、过滤条件和索引结构互相配合。
索引分类别死背,要看它解决什么
文中按字段特性列了主键索引、唯一索引、普通索引和前缀索引。主键索引建在主键字段上,一张表最多一个,不允许空值;唯一索引要求索引列值唯一,但可以有空值;普通索引没有唯一约束,只是帮助查询;前缀索引则只取字符字段前几个字符建索引,用来减小索引体积,适合较长字符串列。
这些分类看起来像考点,实际都是设计选择。用户 ID、订单 ID 这类天然唯一且用于定位记录的字段,适合作为主键或唯一索引。昵称、标题、描述这类长文本,如果确实要按前缀查,可以考虑前缀索引,但要注意区分度。区分度太低,索引效果会变差。
按字段个数又可以分为单列索引和联合索引。单列索引只建立在一个列上,比如主键索引;联合索引由多个列组合而成,适合多列条件查询。联合索引不是把几个单列索引简单摆在一起,它有顺序,有匹配规则,也会影响优化器能不能真正用上。
联合索引要记住最左匹配
资料 对最左匹配原则的解释很直接:使用联合索引时,要按最左优先的方式匹配,查询条件应该从索引最左边的列开始,不能跳过中间列。如果查询条件不按索引顺序匹配,索引可能失效。
假设有联合索引 `(column1, column2, column3)`。查询 `where column1 = 'value1'` 可以用;查询 `where column1 = 'value1' and column2 = 'value2'` 也可以用;查询 `where column2 = 'value2'`,或者只查 `column2` 和 `column3`,就不满足最左匹配。因为这棵索引是按 column1 开始组织的,跳过最左列,MySQL 很难直接利用它定位范围。
还有一个细节:遇到范围查询时,匹配会停在范围字段后面。比如 `(a, b, c)` 上建联合索引,条件是 `a = 1 and b > 10 and c = 3`,一般可以用到 a 和 b,b 后面的 c 不一定还能继续用于精确定位。写联合索引时,等值条件、范围条件、排序字段、区分度都要一起考虑。
索引下推是在减少无效回表
资料 最后讲到索引下推,也就是 index condition pushdown。它解决的问题是:联合索引里有些条件不能完全用于定位,但字段本身还在索引里,能不能先在索引遍历阶段判断掉一部分记录,减少回表次数?MySQL 5.6 引入的索引下推就是做这件事。
相关的例子是 `where name like '张%' and age = 10 and ismale = 1`。按前缀索引规则,搜索索引树时可能只能先用“张”找到满足前缀的记录。如果没有索引下推,就要从这些候选记录开始一个个回表,再到主键索引上取整行数据,对比 age 和 ismale。这样回表次数会更多。
有了索引下推后,MySQL 可以在遍历联合索引时,先对索引中包含的字段做判断,过滤掉不满足条件的记录,再回表。文中的例子从回表 4 次减少到回表 2 次。它不是让索引规则失效,也不是让所有条件都能随便用索引,而是在已有索引字段范围内,尽量少做无效回表。
排查慢查询时怎么用这些知识
排查慢查询,不要一上来就说“加索引”。先看 SQL 的过滤条件、返回字段、排序字段,再用 `EXPLAIN` 看执行计划。文中提到,可以在 SQL 前加 explain 观察是否使用索引,`type=ALL` 通常表示全表扫描。不同 MySQL 版本和输出字段会有差别,但这一步能把猜测变成证据。
如果发现走了二级索引但仍然慢,要看回表次数是不是太多,返回字段能不能被覆盖索引满足。如果联合索引没有命中,要检查是否跳过最左列,是否在范围查询后继续期待后续列精确匹配。如果索引很多但优化器没选,也要看条件区分度、统计信息和 SQL 写法。
索引学习到这里,可以先记住一句实用的话:索引不是越多越好,而是让常见查询少扫描、少读页、少回表。能用主键直接查,就别绕一圈;能覆盖索引,就别随手 `select *`;联合索引要按查询模式设计,不要把几个字段随意拼起来。
常见问题
MySQL 索引为什么常用 B+ 树?
因为数据库索引通常要落到磁盘。B+ 树这类多叉树能降低树高,减少查询过程中读取数据块的次数,同时也适合范围查询。
什么是回表?
按二级索引查到主键值后,再回到主键索引里查整行数据,这个过程叫回表。主键索引叶子节点存整行,二级索引叶子节点通常存主键值。
覆盖索引一定比回表好吗?
大多数读取场景下,覆盖索引能减少一次主键索引查询,通常更快。但是否值得为覆盖索引增加字段,要结合写入成本、索引大小和查询频率判断。
联合索引的最左匹配原则怎么记?
把联合索引看成有顺序的组合,例如 `(a, b, c)`。查询从 a 开始匹配通常能用上,跳过 a 直接查 b 或 c 就很难用好这个索引;遇到范围查询后,后续字段的匹配能力也会受影响。