MySQL 索引
1. 什么是索引?索引的本质、优缺点、底层逻辑
索引类似于书籍的目录,可以减少扫描的数据量,提高查询效率。
- 如果查询的时候,没有用到索引就会全表扫描,这时候查询的时间复杂度是 O(N)
- 如果用到了索引,那么查询的时候,可以基于二分查找算法,通过索引快速定位到目标数据, MySQL 索引的数据结构一般是 B+ 树,其搜索复杂度为 O(logdN),其中 d 表示节点允许的最大 子节点个数。
索引是数据库存储引擎用于快速查询数据的排序数据结构 ,本质是以空间换时间。
-
作用:避免全表扫描,减少磁盘随机 IO,大幅提升 SELECT、WHERE、ORDER BY、GROUP BY、JOIN 效率。
-
优点:查询速度指数级提升,降低数据库 CPU 与 IO 压力。
-
缺点:
- 占用额外磁盘空间,索引越大空间开销越高。
- INSERT/UPDATE/DELETE 时需同步维护所有索引,降低写入性能。
- 索引过多会导致优化器选择困难,甚至选错索引。
-
适用场景:高频查询、大表、筛选性强的字段;不适用小表、重复度极高字段、频繁写入字段。
2. InnoDB 为什么选用 B+ 树作为索引结构,而不是 B 树、哈希、红黑树
-
红黑树:树太高,磁盘 IO 次数多,不适合磁盘存储。
-
哈希索引:仅支持等值查询,不支持范围、排序、模糊查询,限制极大。
-
B 树:叶子节点不相连,范围查询需回溯父节点,效率低。
-
B+ 树优势:
- 非叶子节点只存键值,不存完整数据,单次 IO 加载更多索引项,树更矮更胖,IO 次数极少。
- 所有数据都存在有序双向链表叶子节点,范围查询、分页、排序极快。
- 查询复杂度稳定 O (log n),所有查询都走到叶子节点,性能稳定。
- 适合磁盘预读特性,充分利用操作系统页缓存。
3. 聚簇索引 vs 非聚簇索引,InnoDB 与 MyISAM 索引差异
-
聚簇索引
索引结构与行数据物理存储在一起,叶子节点存储整行完整数据。
InnoDB 主键索引就是聚簇索引,一个表只能有一个。
-
非聚簇索引(二级索引)
索引与数据分离,叶子节点只存主键值,查询需先查索引再回表查数据。
InnoDB 的普通索引、唯一索引、联合索引都属于二级索引。
-
引擎区别
InnoDB:聚簇索引组织整张表,必须有主键,无主键会自动生成隐藏 rowid。
MyISAM:索引与数据完全分离,所有索引都是非聚簇,叶子节点存数据行地址。
聚簇索引如何选择?
- 如果有主键,默认会使用主键作为聚簇索引的索引键(key);
- 如果没有主键,就选择第一个不包含 NULL 值的唯一列作为聚簇索引的索引键(key);
- 在上面两个都没有的情况下,InnoDB 将自动生成一个隐式自增 id 列作为聚簇索引的索引键 (key);
其它索引都属于辅助索引(Secondary Index),也被称为二级索引或非聚簇索引。创建的主键索引 和二级索引默认使用的是 B+Tree 索引。
4. 主键索引、唯一索引、普通索引、联合索引、全文索引区别与使用场景
-
主键索引(PRIMARY KEY)
非空、唯一、不可重复,一个表只能一个,聚簇索引,性能最高。
-
唯一索引(UNIQUE)
列值唯一,允许一个 NULL,约束性强,适合手机号、身份证。
-
普通索引(INDEX)
仅加速查询,无约束,最常用。
-
联合索引
多字段组合索引,遵循最左匹配,减少冗余索引,适合多条件组合查询。
-
全文索引(FULLTEXT)
针对长文本高效搜索,支持
MATCH AGAINST,不支持模糊查询优化。
5. 最左匹配原则深度解析,包含范围查询、跳过字段、底层逻辑
联合索引是按字段顺序排序的,查询必须从左到右连续匹配,中断则后续字段无法使用索引。
例:索引 idx(a,b,c)
-
a=1→ 全命中 -
a=1 and b=2→ 全命中 -
a=1 and c=3→ 仅命中 a,c 失效 -
b=2→ 完全失效 -
a=1 and b>2 and c=3→ a、b 命中,c 失效(范围查询会中断后续匹配)
sql
a=1 and b like 'xx%'
→ 仍可命中前两列
底层原因:B + 树按索引字段依次排序,不按最左开始就无法定位树节点。
6. 回表查询、覆盖索引、索引合并、索引排序深度解释
-
回表:通过二级索引查到主键,再去聚簇索引查完整数据,多一次 IO,影响性能。
-
覆盖索引:查询字段全部包含在索引中,无需回表,性能极高。
例如:
sqlselect name from user where age=18索引
(age,name)即为覆盖索引,因为查询的是name,在where子句里的是age。 -
索引合并:MySQL 将多个单列索引结果合并,效率低于联合索引,尽量避免。
-
Using index:EXPLAIN 中出现代表使用覆盖索引,无回表。
-
Using filesort:无法用索引排序,需内存 / 磁盘排序,性能差。
7. 索引失效 12 种高频场景(深挖版)
LIKE '%xxx'左模糊,无法定位索引范围- 索引列使用函数:
DATE(create_time)、UPPER(name) - 索引列运算:
age+1、price*2 - 隐式类型转换:字符串字段传数字、数字字段传字符串
- 使用
!=、<>、NOT IN、NOT EXISTS、IS NOT NULL - OR 连接条件中有非索引列
- 违反最左匹配原则
- 优化器判断全表更快(小表、索引选择性极差)
- ORDER BY 字段与索引顺序不一致
- GROUP BY 无法使用索引排序
- JOIN 关联字段类型、字符集不一致
- 使用
SELECT *导致无法使用覆盖索引
8. 索引选择性是什么?为何决定索引效率?如何计算?
索引选择性 = 不重复值数量 / 表总行数,值越接近 1 效率越高。
- 高选择性:身份证、手机号、订单号,索引过滤性极强。
- 低选择性:性别、状态(0/1),索引几乎无效,优化器会直接全表扫描。
- 计算:
SELECT COUNT(DISTINCT column)/COUNT(*) FROM table; - 结论:选择性极低的字段不要建索引,浪费空间且拖慢写入。
9. EXPLAIN 执行计划全字段解析
-
id:查询执行顺序,id 越大越先执行。
-
select_type:SIMPLE 简单查询、SUBQUERY 子查询、DERIVED 派生表。
-
table:操作表。
-
type:索引效率(从优到劣)
sqlsystem > const > eq_ref > ref > range > index > ALLALL = 全表扫描,必须优化。
-
key:实际使用索引,NULL = 未命中。
-
key_len:索引使用长度,可判断联合索引是否用满。
-
rows:预估扫描行数,越小越好。
-
Extra:
Using index = 覆盖索引;
Using filesort = 文件排序需优化;
Using temporary = 临时表需优化;
Using where = 使用 where 过滤。
10. 为什么强烈推荐自增主键,不推荐 UUID、随机字符串
- 自增 ID 顺序插入,B + 树叶子节点顺序追加,无页分裂、无数据迁移,写入性能极高。
- 主键长度小,二级索引叶子节点存主键更小,节省大量空间。
- UUID 无序、长度长,插入频繁导致页分裂、节点碎片,查询性能大幅下降。
- 无序主键会导致索引树频繁调整,缓存命中率降低。
- InnoDB 按主键聚簇,顺序主键范围查询更快。
11. 前缀索引原理、使用规则、优缺点、如何选择长度
对字符串前 N 个字符建立索引,减少索引体积。
-
适用场景:长字符串 URL、邮箱、详情内容。
-
选择长度:
测试选择性,
sqlSELECT COUNT(DISTINCT LEFT(col, N))/COUNT(*)接近全列选择性即可。
-
优点:索引更小,写入更快,内存命中率更高。
-
缺点:
无法使用覆盖索引;
无法完成索引排序;
区分度不足会导致回表增加。
12. 索引下推 ICP(Index Condition Pushdown)原理与作用
MySQL 5.6+ 支持的优化机制,在索引层直接过滤不符合条件的数据,减少回表次数。
例:select * from user where name like 'L%' and age=20;
- 无 ICP:先通过 name 查索引得到主键,回表查询所有行再判断 age。
- 有 ICP:在索引节点直接判断 age 条件,过滤无效数据后再回表。
- 默认开启:
show variables like 'optimizer_switch'; - 优势:大幅减少回表 IO,提升组合条件查询效率。
13. 什么是页分裂、页合并?对索引性能的影响
InnoDB 数据按页(16KB)存储,索引也是按页管理。
-
页分裂:插入无序数据导致索引页满,分裂成新页,数据离散,查询 IO 增加。
-
页合并:删除数据导致页过空,后台合并页,消耗资源。
-
影响:
频繁页分裂会导致索引碎片、查询变慢、空间浪费。
-
避免:使用自增主键、避免随机 ID 插入、定期 OPTIMIZE TABLE 整理碎片。
14. 索引碎片产生原因、危害、清理方法
-
产生:大量 UPDATE/DELETE/ 随机 INSERT 导致页分裂、空间空洞。
-
危害:查询扫描更多页,IO 上升,速度变慢,空间浪费。
-
查看:
SHOW TABLE STATUS LIKE 'table_name'; -
清理:
OPTIMIZE TABLE(InnoDB 实际重建表 + 索引)- 导出数据→重建表→导入
- 业务低峰期执行,避免锁表影响线上
15. 企业级索引设计最佳实践(面试官最爱问的总结题)
- 只为高频查询、高选择性字段建索引。
- 多用联合索引,少建单列索引,避免冗余。
- 联合索引区分度高字段放左侧,满足最左匹配。
- 尽量设计覆盖索引,避免回表。
- 禁止在索引列使用函数、运算、隐式转换。
- 控制单表索引数量,一般3~5 个最佳。
- 大字符串使用前缀索引降低空间占用。
- 避免
SELECT *,只查业务需要字段。 - 小表、低选择性字段不建索引。
- 定期通过 EXPLAIN 优化慢查询,删除无用索引。
- 优先自增主键,杜绝无序 UUID。
- 联表查询保证关联字段类型、字符集、索引一致。
16. B 树和 B+ 树的核心区别是什么?为什么 InnoDB 选择 B+ 树?
- B 树:所有节点(叶子 + 非叶子)都存储数据;叶子节点互不相连;范围查询需要回溯父节点。
- B+ 树 :只有叶子节点存储完整数据 ,非叶子节点只存索引键,用于导航;所有叶子节点由双向链表连接。
InnoDB 选择 B+ 树的原因:
- 非叶子节点不存数据,单个页面能容纳更多索引项,树更矮胖,磁盘 IO 次数更少。
- 叶子节点链表结构,对
ORDER BY、BETWEEN、>、<等范围查询极其友好。 - 查询复杂度稳定为 O (log n),任何查询都必须走到叶子节点,性能更可预测。
- 更适配磁盘预读特性,充分利用页缓存,整体 IO 效率远高于 B 树。
17. B+ 树高度一般有多高?千万级大表索引通常几层?
InnoDB 默认页大小 16KB。以 BIGINT 主键(8 字节)+ 指针(6 字节)为例,一个非叶子节点大约可存放 1170 个键。
- 1 层 B+ 树:仅叶子节点,存少量数据。
- 2 层 B+ 树:可支持约 1170 × 约 16KB 数据量。
- 3 层 B+ 树:可支持 1170×1170 ≈ 136 万条数据。
- 4 层 B+ 树:可支持上亿甚至十几亿条数据。
实际工程中,千万级数据表的 B+ 索引高度通常只有 2~3 层,意味着一次查询最多 2~3 次磁盘 IO,效率极高。
18. B+ 树的叶子节点为什么要设计成双向链表?
主要为了支持高效的范围查询与排序:
- 找到起始叶子节点后,可以直接沿着链表顺序遍历,无需回溯上层节点。
- 天然支持
ORDER BY、GROUP BY、LIMIT分页、BETWEEN ... AND ...等业务常用逻辑。 - 双向链表支持正向、反向遍历,实现
ASC/DESC排序成本极低。 - 可以实现顺序 IO,比随机 IO 快一个数量级。
如果没有这条链表,范围查询会退化为多次随机 IO,性能会大幅下降。
19. 为什么说 B+ 树是专为磁盘设计的数据结构?
磁盘访问速度远低于内存,数据库索引设计核心目标是减少磁盘 IO 次数。
B+ 树完全贴合磁盘特性:
- 矮胖结构:层级少,IO 次数极少。
- 节点大小对齐磁盘页:16KB 刚好匹配操作系统与 InnoDB 页大小,充分利用预读。
- 顺序 IO 友好:叶子链表连续遍历,充分发挥磁盘顺序读写优势。
- 稳定的查找路径:所有查询都走到叶子,不会出现极端慢查询。
- 对缓存友好:非叶子节点常驻内存概率高,进一步降低 IO。
20. 聚簇索引和二级索引的 B+ 树结构有什么不同?
-
聚簇索引(主键索引)B+ 树
叶子节点存储的是整行完整数据,查到叶子就拿到了所有字段,不需要再回表。
-
二级索引(普通 / 联合 / 唯一索引)B+ 树
叶子节点只存储索引列 + 主键值,不存整行数据。
查询流程是:
二级索引树 → 找到主键 → 再去聚簇索引树查完整数据 → 即回表查询。
这也是为什么二级索引查询通常比主键查询慢的根本原因。
21. B+ 树的页分裂、页合并是什么?对性能有什么影响?
InnoDB 以页(16KB)为单位管理索引与数据。
- 页分裂:插入数据时,索引页已满,会分裂成两个新页,重新平衡树结构。
- 页合并:删除数据后,页面过空,InnoDB 会将相邻页合并,回收空间。
影响:
- 频繁分裂、合并会产生索引碎片,导致查询 IO 变多、速度变慢。
- 自增主键插入是顺序追加,几乎不触发页分裂,性能极高。
- UUID、随机 ID 插入会频繁引发页分裂,写入性能急剧下降。
- 碎片过多会导致索引占用空间变大、缓存命中率下降。
22. 为什么不推荐用 UUID 做主键,而推荐自增 ID?
UUID 的全称是 Universally Unique Identifier(通用唯一识别码)。
简单理解,它就是一个通过算法生成的、全球唯一的字符串(或数字),通常用来作为数据库记录的唯一标识。
- 自增 ID 是有序的,插入时只在 B+ 树尾部追加,几乎无页分裂,写入性能高。
- 自增 ID 长度小(BIGINT 8 字节),二级索引存储压力小,节省大量空间。
- UUID 无序、长度长(36 字节),插入时随机分布,导致频繁页分裂、索引碎片严重。
- 无序主键会破坏聚簇索引的顺序性,范围查询、分页查询性能变差。
- 更长的主键会让所有二级索引同步变大,内存、磁盘成本翻倍。
23. B+ 树索引与哈希索引的区别,各自适用场景?
-
B+ 树索引
支持等值查询、范围查询、排序、分组、模糊查询
like 'prefix%'。适用:绝大多数业务查询场景,是 InnoDB 默认索引。
-
哈希索引
仅支持精准等值查询 (
=、IN),不支持任何范围、排序、模糊查询。适用:Memory 引擎、精确匹配极高的场景。
InnoDB 只有自适应哈希索引,不能手动建哈希索引,因为业务上范围、排序需求远多于纯等值查询。
24. 什么是全索引扫描(type: index)?和全表扫描有什么区别?
全索引扫描是遍历整个索引 B+ 树的叶子节点链表,而不是遍历数据表。
出现场景:
- 查询没有 WHERE 条件,但需要遍历索引
COUNT(*)走了二级索引- 违反最左匹配,但仍可以通过索引覆盖完成查询
与全表扫描(ALL)区别:
- index 只遍历索引,数据量更小;ALL 遍历整表数据。
- index 顺序 IO 更快;ALL 可能产生更多随机 IO。
- index 效率优于 ALL,但仍属于需要优化的类型。
25. B+ 树查找一条数据的完整流程是怎样的?
- 从 B+ 树根节点开始加载到内存。
- 在节点内部通过二分查找,定位到下一层子节点指针。
- 加载对应子节点,重复二分查找步骤。
- 一直向下,直到到达叶子节点。
- 如果是聚簇索引:直接读取叶子节点上的完整数据。
- 如果是二级索引:拿到主键值,再回到聚簇索引执行一遍查找流程,即回表。
整个过程路径极短、IO 极少,这是索引能大幅提速的根本原因。
26. 什么是联合索引?为什么实际开发中优先推荐联合索引,而不是多个单列索引?
答案:
联合索引是指将多个字段组合在一起建立一个索引 ,例如 idx(a, b, c)。
优先使用联合索引的原因:
-
筛选效率更高
多条件查询时,联合索引能一次性通过多个字段缩小数据范围,过滤性远强于多个单列索引。
-
避免索引合并
多个单列索引在多条件查询时,MySQL 可能会使用索引合并(index merge),但效率远低于一个设计良好的联合索引。
-
节省空间
一个联合索引占用空间远小于多个独立单列索引之和,减少磁盘和内存开销。
-
更容易实现覆盖索引
联合索引天然包含多个字段,更容易让查询字段全部落在索引内,避免回表。
-
优化排序
联合索引本身有序,可以直接用于
sqlORDER BY a, b这类排序,避免 Using filesort。
27. 详细解释联合索引的最左匹配原则,底层原理是什么?
最左匹配原则:使用联合索引 idx(a,b,c) 时,查询条件必须从左到右连续匹配索引字段,不能跳过左边字段直接查右边,否则后面字段无法使用索引。
底层原理:
联合索引在 B+ 树中的存储规则是:
- 先按第一个字段
a排序; a相等时,再按第二个字段b排序;b相等时,再按第三个字段c排序。
整个索引树是按左侧字段有序组织的。如果不从最左开始查,就无法在 B+ 树中进行二分查找定位,只能遍历索引或全表。
28. 联合索引中出现范围查询(>、<、between、like 'prefix%'),为什么会导致后面字段索引失效?
以 idx(a, b, c) 为例:
sql
where a = 1 and b > 100 and c = 3;
a是等值匹配,可以快速定位到一组节点;b是范围查询,会确定一段区间;- 但在这段区间内,
c是无序的,因为 B+ 树只在 b 相同的情况下才对 c 排序。
所以优化器只会使用到 a、b,c 无法走索引。
结论:等值条件放前面,范围条件放最后,否则会截断后面字段的索引。
29. 联合索引的字段顺序如何设计?有什么通用原则?
答案:
经典设计原则:等值在前,范围在后;区分度高在前,常用排序在前。
-
等值查询字段放最前面
如
user_id = ?,能最快缩小范围。 -
区分度(选择性)高的字段靠前
如订单号、手机号 > 状态、类型。
-
范围查询字段放最后
因为范围会截断索引,后面字段失效。
-
排序、分组字段放入索引
让
ORDER BY、GROUP BY直接使用索引有序性,避免文件排序。 -
查询频次高的组合优先建索引
避免为极少出现的查询建索引。
示例:
sql
where a=? and b=? and create_time between ? and ?
索引顺序:(a, b, create_time)
30. 联合索引 (a,b,c),哪些 SQL 能命中索引?哪些不能?(高频面试题)
答案:
索引:idx(a,b,c)
能命中(全部或部分)
where a = 1where a = 1 and b = 2where a = 1 and b = 2 and c = 3where a = 1 and c = 3(只命中 a,c 失效)where a = 1 and b > 2(命中 a、b,c 失效)where a = 1 and b like 'abc%'
完全不能命中
where b = 2where c = 3where b = 2 and c = 3where a like '%abc'
31. 联合索引和多个单列索引,在什么场景下效果差别巨大?
-
多条件组合查询
联合索引一次定位;单列索引需要分别查找再合并,效率低很多。
-
需要排序
联合索引天然有序,可以避免 Using filesort;单列索引做不到。
-
需要覆盖索引
联合索引可直接覆盖查询字段;单列索引很难做到。
-
表数据量大、筛选压力高
单列索引过滤后仍有大量数据,联合索引能大幅减少回表。
只有在每个字段都是独立等值查询、互无关联时,单列索引才勉强可用。
只要是组合查询 + 排序 + 分页,联合索引完胜。
32. 什么是联合索引的 "索引覆盖"?为什么它能极大提升性能?
查询所需的所有字段都包含在联合索引中,不需要回表查聚簇索引,就是覆盖索引。
例如:
索引 idx(a,b,c)
sql
select b,c from t where a=1;
性能提升原因:
- 省去回表操作,减少一次 B+ 树查找;
- 只访问索引页,不访问数据页,IO 大幅减少;
- 数据更紧凑,缓存命中率更高;
- EXPLAIN 中会出现
Using index,代表最优状态。
33. 联合索引存在哪些常见坑?
-
字段顺序乱建
范围在前、区分度低在前,导致索引利用率极低。
-
索引字段过多
超过 3~4 个后,索引体积变大,写入性能下降明显。
-
重复冗余索引已有
(a,b,c),又建(a)、(a,b),浪费空间。 -
认为有索引就一定快?选择性极差的字段(如 status=0/1)放前面,索引形同虚设。
-
忽略排序字段与索引顺序不一致 导致出现 Using filesort。
-
使用
select *无法使用覆盖索引,必回表。
34. 已有联合索引 (a,b),还有必要单独建 (a) 索引吗?
完全没必要,属于冗余索引。
因为最左匹配原则:
- 查询
where a=?可以直接复用(a,b)索引; - 单独建
(a)只会增加写入开销,不提升查询效率; - MySQL 优化器会自动选择更合适的索引,不会因为多建单列索引变快。
同理:
已有 (a,b,c),不需要再建 (a)、(a,b)。
35. 联合索引如何判断是否被充分使用?(结合 EXPLAIN)
主要看 key_len 和 type、Extra。
-
key_len
表示实际使用的索引长度。
- 长度等于整个联合索引长度:全部字段命中;
- 长度只等于前几个字段:后面字段因范围查询或中断而失效。
-
type
ref:等值匹配,索引使用良好;range:范围查询,索引被截断;index:全索引扫描,索引设计不合理。
-
Extra
Using index:覆盖索引,最优;Using where:在存储引擎过滤后,再次在 server 层过滤;Using filesort / Using temporary:索引未覆盖排序,需要优化。
36. 如果有一个字段statue(1/0),那么他适合建索引吗?
不适合。区分度低的字段不适合建索引!!!
37. 索引的优缺点
索引最大的好处就是提高查询速度,但是索引也有缺点,比如:
- 需要占用物理空间,数量越大,张勇空间越大。
- 创建索引和维护索引需要耗费时间,这种时间随着数据量的增加而增大。
- 会降低表的增删改的效率,因为每次增删改索引,B+树为了维护索引的有序性,都需要来动态维护树的平衡。
所以,索引不是万能钥匙,他也是根据场景来使用的
38. 索引优化详细讲讲
常见优化索引的方法:
-
前缀索引优化
使用前缀索引是为了减小索引字段大小,可以增加一个索引页中存储的索引值,有效提高索引的查询速度。在一些大字符串的字段作为索引时,使用前缀索引可以帮助我们减小索引项的大小。
-
覆盖索引优化
覆盖索引是指 SQL 中 query 的所有字段,在索引 B+Tree 的叶子节点上都能找得到的那些索引,从二级索引中查询得到记录,而不需要通过聚簇索引查询获得,因此可以避免回表的操作。
-
主键索引最好是自增的
-
如果我们使用自增主键,那么每次插入的新数据就会按顺序添加到当前索引节点的位置,不需要移动已有的数据,当页面写满,就会自动开辟一个新页面。因为每次插入一条新记录,都是追加操作,不需要重新移动数据,因此这种插入数据的方法效率非常高。
-
如果我们使用非自增主键,由于每次插入主键的索引值都是随机的,因此每次插入新的数据时,就可能会插入到现有数据页中间的某个位置,这将不得不移动其它数据来满足新数据的插入,甚至需要从一个页面复制数据到另外一个页面,我们通常将这种情况称为页分裂。页分裂还有可能会造成大量的内存碎片,导致索引结构不紧凑,从而影响查询效率。
-
防止索引失效:
- 当我们使用左或者左右模糊匹配的时候,也就是 like % xx 或者 like % xx% 这两种方式都会造成索引失效;
- 当我们在查询条件中对索引列做了计算、函数、类型转换操作,这些情况下都会造成索引失效;
- 联合索引要能正确使用需要遵循最左匹配原则,也就是按照最左优先的方式进行索引的匹配,否则就会导致索引失效。
- 在 WHERE 子句中,如果 OR 连接的条件里有任何一个没有建立索引,通常就会导致索引失效、走全表扫描(和前后顺序无关)。MySQL 5.1 起引入了 index_merge 优化,当满足条件时优化器可能会对多个索引做合并(union /intersect)读取,但并不总是生效,依赖具体数据分布与优化器成本估算。