第一部分:索引基础与数据结构
面试官通常会用最简单的问题开场,但你的回答要稳,展现出扎实的基本功。

1.什么是索引?为什么需要它?
一句话定义:索引是一种用于快速查询数据的有序数据结构。
它就像一本书的(Page
Directory),通过二分查找的方式,可以快速定位到行数据在页内的槽位。
所以,B+树是宏观上的索引,页目录是微观上的索引。
7.索引的代价
空间代价:每建立一个索引,都要为其维护一棵
B+树,每一棵树都要占用磁盘空间。
时间代价:对数据进行增、删、改操作时,不仅要修改表数据,还要同时维护所有的索引树。
这会导致写操作性能下降。
同时,数据在页内的移动、页的分裂等也会增加开销。
/>
第三部分:索引使用策略与EXPLAIN
实战
理论懂了,接下来就是在实际
SQL
中如何用好索引,以及如何用EXPLAIN诊断问题。
8.索引失效的常见场景(面试高频)
知道什么时候索引生效,更要清楚什么时候会失效。
以下情况,索引很可能不会被使用:
不满足最左前缀原则:对于联合索引
(a,c),如果查询条件只包含
b或c,而缺少a,则索引失效。使用
!=或<>操作符:在某些情况下,会导致索引失效,变成全表扫描。索引列上使用了函数或计算:例如
whereYEAR(create_time)
10。
解决方案:应该把函数或计算放在等式右侧,如
wherecreate_time
'2024-01-01'。
类型隐式转换:例如,索引列
user_id是varchar类型,但查询时用的是数字:whereuser_id
会将字符串转为数字,相当于在索引列上用了
CAST函数,导致索引失效。like以通配符%开头:wherename
B+树索引是按索引值的最左部分进行排序的,所以无法确定以
%开头的字符串在树中的位置。wherename
'张三%'则可以用到索引。
使用
OR连接:如果OR前后的条件中,有一个列没有索引,那么即便另一个列有索引,也可能导致索引失效。解决方案:改为两个查询用
UNION连接,或者确保OR两边的列都有独立的索引。NOTIN、
NOTEXISTS:在某些情况下会导致全表扫描。
数据分布:如果优化器认为走索引还不如直接全表扫描快(例如查询的数据量占全表很大比例),它可能会放弃索引。
9.SQL
性能?
EXPLAIN是
SQL
优化的利器。
重点关注以下几个字段:
type:连接类型,是衡量查询好坏的重要指标。性能从好到差依次是:
system:表只有一行记录(系统表),是const类型的特例。const:通过主键或唯一索引一次就找到了,速度极快。eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录匹配。常见于主键或唯一索引作为关联条件。
ref:非唯一性索引扫描,返回匹配某个单独值的所有行。range:只检索给定范围的行,使用一个索引来选择行。如
between、<、>、in()等。index:FullIndex
Scan,扫描了整个索引树。
比全表扫描好一点,因为索引树通常比表数据小。
ALL:全表扫描,性能最差,需要尽量避免。
possible_keys:指出MySQL
可能用到的索引。
key:实际使用的索引。如果为
NULL,则表示未使用索引。key_len:使用的索引字节数。在不损失精度的情况下,值越小越好。
它可以帮助判断联合索引中真正用到了哪几列。
rows:根据统计信息预估的必须检查的行数。值越小越好。
Extra:包含额外的、非常重要的信息。Usingindex:覆盖索引。
查询的所有列都在索引中,无需回表。
性能很棒!
Usingwhere:表示
MySQL
在存储引擎层返回结果后,又进行了条件过滤。
Usingindex
condition:索引下推(ICP)。
MySQL
5.6
后的优化,在遍历索引过程中,先对索引包含的字段进行条件判断,过滤掉不满足条件的记录,减少回表次数。
Usingtemporary:使用了临时表保存中间结果,常见于
GROUPBY。
性能差,需优化。
Usingfilesort:无法利用索引完成排序操作,需要进行额外的排序操作(可能在内存或磁盘)。
性能差,需优化。
10.什么是回表、覆盖索引、索引下推?
回表:如上所述,从二级索引查到主键后,再到聚簇索引查找完整数据的过程。
覆盖索引:查询的所有字段都包含在同一个二级索引中。
这样,直接从二级索引中就能获取到所需数据,无需回表。
这是非常有效的优化手段。
例如:有联合索引
(name,age),执行
selectname,
'张三',此时该联合索引就是覆盖索引。
索引下推(ICP):也是针对联合索引的优化。
在没有
ICP
时,存储引擎根据索引定位到数据行后,会返回给
Server
ICP,可以在存储引擎层直接判断索引中包含的其他字段的条件,提前过滤掉不符合的数据,减少回表次数和数据传输。
例如:联合索引
(name,age),查询
wherename
时,会找到所有
name以“张”开头的记录,然后全部回表。有
ICP
10这个条件,只对符合条件的记录回表。
/>
第四部分:高级索引优化与实战
理论扎实了,我们来探讨一些更复杂的场景和优化技巧。
11.联合索引字段顺序如何选择?
这是一个高频面试题。
原则如下:
区分度高的列放前面:让查询能尽快地缩小范围。
区分度
col)
count(*),值越大,区分度越高。
查询频率高的列放前面:优先满足最左前缀原则,让索引被更多查询用到。
考虑空间大小:在区分度和频率相近的情况下,选择占用空间较小的字段,因为非叶子节点能存储更多的
key,降低树高。
避免不必要的排序:如果查询中经常需要对字段
a排序,对b分组,那么(a,a)更优,因为索引本身是有序的,可以避免
filesort。
12.前缀索引
定义:对于很长的字符串列(如
varchar(255)),可以只索引字符串的前几个字符,而不是整个字符串。语法:
altertable
(name(10));
优点:大大节省索引空间,提高索引的查询效率。
缺点:无法使用覆盖索引,并且可能降低索引的区分度(增加扫描行数)。
选择长度:目标是找到“足够高区分度”的最短长度。
可以通过计算不同长度的区分度,选择一个与完整列区分度最接近的长度。
13.索引合并
定义:MySQL
5.0
及以后引入的优化。
当
WHERE条件中包含了多个条件,且这些列分别有单列索引时,MySQL可能会分别用这些索引进行扫描,然后将结果合并(取交集
INTERSECT、并集UNION等)。例子:
selectfrom
10;,假设
name和age都有索引。局限性:虽然能用,但通常不如建立一个合适的联合索引高效。
因为索引合并需要读取多个索引树,并进行合并操作,消耗
CPU
和内存。
可以通过
optimizer_switch关闭。
14.索引与排序、分组
ORDERBY:如果
ORDERBY的字段顺序与索引的列顺序完全一致,且排序方向(
ASC/DESC)也一致,那么MySQL
可以直接利用索引的有序性,避免
filesort。GROUPBY:
GROUPBY本质上也是先排序后分组(或直接利用索引顺序分组)。
因此,
GROUPBY类似,利用索引可以避免创建临时表。
15.分页查询优化(深分页问题)
问题:
selectfrom
行,代价极高。
优化方案:
延迟关联
子查询:先利用覆盖索引快速找到需要的行的主键,再通过主键关联回原表获取其他列。
sql
select
from
t2.id;
书签记录:记录上一页的最大
id,下一页查询时带上where>
10。
这种方式只能用于排序字段唯一且连续的情况(如自增主键)。
/>
第五部分:常见面试连环炮
最后,我们模拟几组面试中可能出现的“组合拳”,看看你能不能接住。
场景一:慢
SQL
排查
Q1:线上的一个查询突然变慢了,你怎么排查?
A:
开启慢查询日志,找到对应的慢
SQL。
用
EXPLAIN分析该SQL
的执行计划,重点关注
type、key、rows、Extra。判断是否没用到索引,或者索引失效。
看
rows的值,判断扫描行数是否过大。看
Extra是否有Usingtemporary,考虑优化排序和分组。
如果是
count(*)变慢,可能是数据量太大,考虑用汇总表或改用近似值。如果索引都用上了但还是慢,可能是数据本身太大,或者需要查的数据分布在大量离散的页中,导致大量随机
I/O,这时可以考虑是否能改成覆盖索引。
场景二:联合索引的最左前缀
Q2:有一个联合索引
(a,c),以下查询能否用到索引?
where=
3:能,全部用到。
where=
b,最左前缀中断。
where=
3:不能,因为缺少
=
无法使用索引(除非是特殊的索引条件下推)。
where(1,2)
都能用到。
in在一定条件下会被优化器优化为多个等值条件,不算范围查询,所以仍可用。
where=
a,同时利用索引避免
filesort。因为索引本身就是按
排序,a
filesort)。
因为索引顺序是
->
的顺序。
场景三:为什么要用自增列作主键?
Q3:InnoDB
表为什么推荐用自增主键?
性能角度:自增主键是顺序插入的,每次插入新数据都是在当前索引页后面追加,页分裂的概率很低,索引结构紧凑,数据存储紧凑。
存储角度:如果是业务字段(如身份证号)作主键,值是无序的,插入可能导致大量的页分裂和页移动,产生很多碎片,效率低下。
同时,主键长度越大,二级索引的叶子节点就越大,占用更多磁盘空间,因为每个二级索引都保存着这个主键值。
注意:在分库分表场景,或需要提前规划数据分布时,可能会使用雪花算法等生成有序但不连续的分布式
索引九阳神功
索引的本质:一种排好序的、能快速查找的数据结构(B+树)。
索引的代价:占空间、拖慢写操作。
索引的设计:
为区分度高、查询频繁的列建立索引。
合理利用联合索引,将最常用的列放最左边。
避免建立过多单列索引,优先考虑联合索引。
索引的使用:
写
SQL
时,避免在索引列上使用函数、计算、类型转换。
like查询时,避免%开头。能用
union替代or时,尽量使用。
索引的分析:
熟练使用
EXPLAIN,看懂type、key、Extra。追求
ref、range及以上级别的type。争取
Usingindex(覆盖索引),警惕
Usingtemporary。
索引的优化:
利用索引下推减少回表。
利用覆盖索引避免回表。
用延迟关联解决深分页。
字符串过长考虑前缀索引。


