MySQL 索引面试通俗总结一、什么是索引?通俗理解把索引想象成一本书的目录,是不是一下子就明白了?设想一下,一本 1000 页的书,你要找“事务”这个词:没有目录的话,只能从第一页开始一页页翻,这就是全表扫描。有目录就方便多了,先查到“事务在第 500 页”,然后直接翻过去,这就是使用索引查询。所
把索引想象成一本书的目录,是不是一下子就明白了?

长期稳定更新的攒劲资源: >>>点此立即查看<<<
设想一下,一本 1000 页的书,你要找“事务”这个词:
所以索引的本质,其实就是:
帮助 MySQL 快速找到数据的一种数据结构,本质上是“用空间换时间”的设计。
当然,天下没有免费的午餐。索引会额外占用磁盘空间,而且在你新增、修改、删除数据的时候,MySQL 也得同步维护索引,这可是要付出代价的。
MySQL 索引是一种帮助存储引擎快速查找数据的数据结构,可以理解为数据库表的目录。没有索引时,MySQL 可能需要逐行扫描;有索引后,可以快速定位数据。不过索引需要占用空间,也会增加增删改时的维护成本,所以索引并不是越多越好。
面试里最常被问到的,主要是这四种分类方式。
InnoDB 最常用的是 B+Tree 索引,这点必须记住。
这些分类并不是互斥的。一个索引完全可以同时属于多个类别,比如:
B+Tree 索引+二级索引+普通索引+联合索引。
完全可以把它想象成商场的多级导航指引:
第一层:食品区、服装区、电器区 ↓第二层:饮料区、零食区、粮油区 ↓第三层:可乐、牛奶、果汁
你想找可乐,不需要把整个商场逛一遍,按照分类一层一层往下找就行了。
别把 B+Tree 简单理解成二分法,那就太局限了。
二叉树一个节点通常只有两个分支,而 B+Tree 一个节点可以有很多个分支,所以它是一棵多叉树。分支越多,树就越矮,查找数据时需要访问磁盘的次数自然就越少。
千万级数据量的 B+Tree,通常只需要维持在三四层左右。这意味着,找到一条数据通常只需要少量磁盘 I/O。
二叉树每个节点只有两个分支,数据量一大,树就会变得很高,查询一条数据可能需要访问很多层,磁盘 I/O 次数自然就多了。
Hash 做等值查询确实很快:
WHERE id = 1001
但遇到范围查询就抓瞎了:
WHERE id BETWEEN 1000 AND 2000
因为 Hash 中的数据不是按照大小顺序排列的,而 B+Tree 的叶子节点是有序连接的,它更适合数据库常见的等值查询、范围查询和排序操作。
InnoDB 使用 B+Tree,主要是因为 B+Tree 是多叉树,树的高度比较低,可以减少磁盘 I/O。它的数据集中存储在叶子节点,单个非叶子节点可以保存更多索引。同时叶子节点有序连接,非常适合范围查询和排序。相比之下,二叉树层数较高,Hash 虽然等值查询快,但不适合范围查询。
假设有一张商品表:
CREATE TABLE product ( id BIGINT PRIMARY KEY, product_no VARCHAR(50), name VARCHAR(100), price DECIMAL(10, 2), INDEX idx_product_no(product_no));
主键 id 对应的索引就是聚簇索引。它的叶子节点里,存的是整行完整数据:
id + product_no + name + price + 其他完整数据
可以理解成:
主键目录后面直接放着完整档案,查到了就拿到了全部信息。
product_no 对应的是二级索引。它的叶子节点里,主要保存的是:
product_no + 主键id
可以理解成:
商品编码目录只记录商品编码和对应的档案编号,完整的档案还在主键目录里。
InnoDB 中,如果表有主键,就使用主键作为聚簇索引;如果没有主键,就会尝试选择一个不允许为 NULL 的唯一列;如果连这个也没有,InnoDB 会自己生成一个隐藏的聚簇索引键。
InnoDB 的主键索引属于聚簇索引,叶子节点保存完整行数据。普通索引属于二级索引,叶子节点保存索引字段和对应的主键值。因此通过二级索引查询完整数据时,可能还需要根据主键再次查询聚簇索引。
执行这条 SQL:
SELECT *FROM productWHERE product_no = 'P1001';
查询过程是这样的:
先查询 product_no 二级索引 ↓找到对应的主键 id ↓再根据 id 查询主键索引 ↓获得完整商品数据
查了两棵 B+Tree,这个过程就叫回表。
这就好比你在“小区住户姓名目录”里查到:
张三住在 3 栋 502
然后还得亲自跑到 3 栋 502 去找张三。第一次查目录,第二次找完整信息,这就是回表。
回表是指通过二级索引查询时,先在二级索引中找到主键值,再根据主键值去聚簇索引中查询完整行数据。因为查询了两次 B+Tree,所以回表次数过多会增加磁盘 I/O。
执行这条 SQL:
SELECT id, product_noFROM productWHERE product_no = 'P1001';
二级索引中本来就保存了:
product_no + id
查询需要的数据已经全部存在于二级索引中,完全不需要再回头去查询主键索引了。
这就叫覆盖索引。
你问物业:“张三住在哪一栋哪一户?”
姓名目录里已经写着“3 栋 502”,物业直接回答你,完全不需要再跑去张三家确认一遍。
覆盖索引是指查询需要的所有字段都能直接从索引中获取,不需要再回到聚簇索引查询完整数据。覆盖索引可以减少回表次数和磁盘 I/O。执行计划的 Extra 中间出现
Using index,一般说明使用了覆盖索引。
例如建立这样一个索引:
CREATE INDEX idx_status_create_timeON orders(status, create_time);
这就是联合索引。
联合索引不是分别创建两个独立的目录,而是按照多个字段共同排序:
先按照 status 排序,status 相同时,再按照 create_time 排序
例如:
待支付: 2026-07-01 2026-07-02 2026-07-03已支付: 2026-07-01 2026-07-02
假设存在一个联合索引:
(a, b, c)
它的排序方式是:
先按 a 排序,a 相同时按 b 排序,a、b 都相同时按 c 排序
以下查询通常可以利用这个联合索引:
WHERE a = 1;WHERE a = 1 AND b = 2;WHERE a = 1 AND b = 2 AND c = 3;
而以下查询则无法正常利用这个联合索引的最左部分:
WHERE b = 2;WHERE c = 3;WHERE b = 2 AND c = 3;
想象一本电话簿,它是按照:
省份 → 城市 → 姓名
这个顺序来排序的。
你知道省份,就可以快速缩小范围:
湖南省
你知道省份和城市,查找起来更容易:
湖南省 → 长沙市
但你只知道姓名:
张三
因为整本目录不是先按照姓名排序的,所以很难直接定位。
联合索引必须从最左边字段开始匹配,本质原因就在于,后面的字段只有在前面字段值相同的情况下,才是局部有序的。
WHERE 条件中字段的书写顺序通常不是关键。
下面这两条 SQL 一般没有本质区别:
WHERE a = 1 AND b = 2;WHERE b = 2 AND a = 1;
MySQL 优化器通常会自动调整条件的顺序,关键还是在于联合索引中是否包含了最左边的字段。
联合索引遵循最左匹配原则,因为联合索引首先按照第一个字段排序,第一个字段相同时才按照第二个字段排序。因此只有从联合索引最左边的字段开始查询,才能利用索引的有序性快速定位数据。
假设有联合索引:
(a, b)
执行查询:
WHERE a > 1 AND b = 2;
通常,a 可以用于确定索引扫描的范围,但一旦进入 a > 1 这个大范围后,b 在整个范围中就不再保持全局有序了,因此 b 通常不能继续用于缩小索引扫描的区间。
可以先记住面试中的常见结论:
联合索引向右匹配时,遇到
>、<这类范围条件,后面的字段通常不能继续用于确定索引扫描范围。
不过,需要特别注意的是,>=、<=、BETWEEN、LIKE '前缀%' 在部分情况下仍可能继续使用后面的联合索引字段。最终情况,还是要结合 MySQL 版本和 EXPLAIN 输出的 key_len 来判断。
面试时不需要一开始就讲得特别复杂,可以先回答:
范围查询字段本身可以使用索引,但范围查询之后的字段是否还能继续参与索引定位,需要结合具体运算符和执行计划来判断。
假设有联合索引:
(name, age)
执行查询:
SELECT *FROM userWHERE name LIKE '郭%' AND age = 25;
在没有索引下推的情况下,MySQL 会先根据 name 找到一批主键,然后全部回表,拿到完整数据后再去判断 age = 25。
查索引 → 回表 → 判断年龄查索引 → 回表 → 判断年龄查索引 → 回表 → 判断年龄
有了索引下推,因为二级索引中已经存在 age 字段,所以 MySQL 可以先在索引内部就完成年龄的判断和过滤。
查索引 ↓先过滤掉年龄不是25岁的记录 ↓只把满足条件的数据回表
索引下推的核心作用就是:
尽量在二级索引内部提前过滤数据,减少不必要的回表次数。
当你看到执行计划 Extra 中间出现:
Using index condition
通常就表示使用了索引下推。
适合创建索引的字段,通常具备以下特点:
WHERE 条件中的字段。JOIN 关联的字段。ORDER BY 的字段。GROUP BY 的字段。举个例子,订单查询:
SELECT id, order_no, status, create_timeFROM ordersWHERE user_id = 1001 AND status = 1ORDER BY create_time DESC;
根据实际查询场景,可以考虑建立:
CREATE INDEX idx_user_status_timeON orders(user_id, status, create_time);
索引不仅能用于筛选数据,还可以利用自身的有序性来帮助排序,一举两得。
表里只有几十条数据,全表扫描可能比走索引更快。
比如性别字段,无非就是“男”、“女”两种值。通过索引查出来全表接近一半的数据,意义不大。
如果一个字段不用于 WHERE、ORDER BY、GROUP BY 和关联查询,建立索引的价值就很有限。
每次修改索引字段,都需要同步维护 B+Tree 结构。比如用户余额经常发生变化,通常不能仅仅因为“可能查询余额”就随便给余额字段建立索引。索引会占用空间,而且会降低增删改的性能。
1、2、3、4、5
新增数据时,通常直接追加到数据页的末尾:
1、2、3、4、5、6
不需要频繁移动已有的数据。
原来数据页里是:
1、3、5、9
突然插入一个:
7
它需要插入到数据页中间。如果数据页已经满了,就可能发生页分裂:
原来一个数据页 ↓拆成两个数据页 ↓移动部分数据
页分裂会增加额外的开销,也可能导致空间利用率降低。
所以,在没有特殊业务要求的情况下,InnoDB 一般建议使用较短的自增主键。主键越短,二级索引占用的空间通常也越小,因为二级索引的叶子节点需要保存主键值。
InnoDB 的数据按照主键顺序存放。使用自增主键时,新数据通常顺序追加,可以减少数据移动和页分裂。如果使用随机主键,数据可能插入到已有数据页中间,增加页分裂和空间碎片。另外,二级索引会保存主键值,所以主键长度也不宜过大。
WHERE name LIKE '%郭';
WHERE name LIKE '%郭%';
索引是从字符串左边开始排序的,不知道开头是什么,就很难快速定位。
下面这种前缀匹配通常可以使用索引:
WHERE name LIKE '郭%';
WHERE age + 1 = 26;
可以改成:
WHERE age = 25;
WHERE YEAR(create_time) = 2026;
可以考虑改成范围查询:
WHERE create_time >= '2026-01-01' AND create_time < '2027-01-01';
字段是字符串类型:
phone VARCHAR(20)
错误写法:
WHERE phone = 13800138000;
更合理的写法:
WHERE phone = '13800138000';
有索引:
(user_id, status, create_time)
只查询:
WHERE status = 1;
通常无法有效使用该联合索引的最左部分。
WHERE user_id = 1001 OR remark = '测试';
如果 user_id 有索引而 remark 没有索引,优化器可能放弃使用索引,转而选择全表扫描。
需要注意:
“存在这些写法”不代表百分之百不使用索引,最终是否使用索引,还是应该通过
EXPLAIN来验证。
使用:
EXPLAINSELECT *FROM ordersWHERE order_no = '202607280001';
重点看以下几个字段。
可能使用的索引。
实际使用的索引。如果:
key = NULL
通常表示没有使用索引。
表示 MySQL 实际使用了联合索引中的多少内容。
预计需要扫描多少行。一般来说,扫描的行数越少越好。
数据访问方式。常见效率从差到好大致是:
ALL→ index→ range→ ref→ eq_ref→ const
重点记忆:
ALL:全表扫描,需要重点关注。index:扫描整个索引。range:索引范围查询。ref:使用普通索引查找。eq_ref:多表关联时使用主键或唯一索引。const:通过主键或唯一索引查询一条确定记录。需要重点关注的信息:
Using filesort
表示不能直接利用索引完成排序,需要额外排序,这通常是个性能隐患。
Using temporary
表示使用了临时表,经常出现在复杂排序或分组中。
Using index
表示使用了覆盖索引,不需要回表,这是比较理想的情况。
Using index condition
表示使用了索引下推。
索引是帮助 MySQL 快速定位数据的数据结构,可以理解为书的目录。它通过额外的存储空间提高查询效率,但也会增加增删改的维护成本。
B+Tree 是多叉树,树高比较低,可以减少磁盘 I/O;数据集中在叶子节点,叶子节点有序连接,适合范围查询和排序。
聚簇索引的叶子节点保存完整行数据,二级索引的叶子节点保存索引字段和主键值。一张 InnoDB 表只有一个聚簇索引,但可以有多个二级索引。
先通过二级索引找到主键,再根据主键去聚簇索引查询完整数据,这个过程叫回表。
查询需要的字段都包含在索引中,可以直接从索引获得结果,不需要回表。
联合索引按照从左到右的字段顺序排序,查询通常需要从最左边字段开始匹配,才能充分利用索引的有序性。
在遍历二级索引时,先利用索引中的其他字段过滤数据,减少不必要的回表次数。
不是。索引会占用磁盘空间,并且新增、修改、删除数据时需要维护索引,会降低写入性能。
自增主键通常是顺序插入,可以减少数据移动和页分裂;同时主键较短,也可以减少二级索引占用的空间。
使用 EXPLAIN,重点查看 type、key、key_len、rows 和 Extra,判断是否使用索引、扫描多少行以及是否发生额外排序、临时表和回表。
索引 = 目录B+Tree = 多层有序目录主键索引 = 目录后面直接放完整档案二级索引 = 目录里保存主键编号回表 = 先查普通目录,再查主键档案覆盖索引 = 普通目录里已经有完整答案联合索引 = 多个字段组成一个有顺序的目录最左匹配 = 必须从目录最左边开始查索引下推 = 回表前先在目录中过滤EXPLAIN = 检查 MySQL 到底怎么查数据
在订单表的设计中,我一般会结合实际的查询场景来构思索引。例如,订单号具有唯一性,可以建立唯一索引;用户经常按照用户 ID、订单状态和创建时间查询订单,可以考虑建立
(user_id, status, create_time)联合索引。同时,会尽量避免直接使用SELECT *,而是通过覆盖索引来减少回表操作。SQL 上线前,一定会使用 EXPLAIN 来检查实际使用的索引、扫描行数,以及是否出现Using filesort或Using temporary等需要警惕的情况。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述