1. 为什么 MySQL 用 B+ 树做索引?不用哈希、二叉搜索树、B 树?

🎯 面试要点

  • 页(Page,默认 16KB)是磁盘 IO 的基本单位,节点 = 页
  • 估算:非叶子节点一个键 + 指针 ≈ 16B,16KB/16B ≈ 1000 个键/页;3 层 = 1000 × 1000 × 16(每叶子约 16 行)≈ 1600 万行
  • B+ 树叶子节点的双向链表是范围查询的关键设计

2. 聚簇索引和非聚簇索引(二级索引)的区别?什么是回表?

覆盖索引示例
-- 表 user(id, name, age, email),索引 idx_name_age(name, age)

-- ① 回表:查 email 不在索引里,要回聚簇索引取整行
SELECT * FROM user WHERE name = '张三';

-- ② 覆盖索引:只用索引列 + 主键,无需回表,快
SELECT id, name, age FROM user WHERE name = '张三';

-- ③ 为什么 id 不用回表:二级索引叶子节点存了主键

🎯 面试要点

  • 主键选型:自增整型优于 UUID(UUID 无序导致页分裂、索引膨胀)——这是"为什么主键要自增"的答案
  • 二级索引要避免"select *"大字段:覆盖不了就回表,列宽还让索引页更大
  • MyISAM 的索引都是非聚簇(叶子存行地址),InnoDB 主键才是聚簇

3. 最左前缀原则是什么?联合索引怎么建?

联合索引 (a, b, c) 底层是一棵 B+ 树,先按 a 排序,a 相同按 b 排序,b 相同按 c 排序。因此查询必须从最左列开始连续匹配才走索引:

联合索引设计原则:

  1. 区分度高的列放前面(性别 0/1 放前面会让索引几乎失效)
  2. 经常范围查询的列放后面(范围列之后的列无法用索引)
  3. 覆盖常用查询(尽量把 select 的列加进去)
  4. 可删除冗余索引:(a) 与 (a,b) 可合并为 (a,b)

🎯 面试要点

  • 范围查询(>/</BETWEEN)之后的列不能用于缩小扫描区间:WHERE a > 1 AND b = 2 → key_len 只到 a。但 MySQL 5.6+ 默认开启 ICP(索引条件下推),b 会在存储引擎层作为索引条件过滤、减少回表(EXPLAIN 出现 Using index condition)
  • LIKE 'xx%' 走索引,'%xx%' 不走(最左前缀同理)
  • 索引下推(ICP):联合索引中,范围后的条件会下推到存储引擎层过滤,减少回表——MySQL 5.6+ 默认开启

4. 索引失效的常见场景(背全)?

  1. 对索引列做函数/运算:WHERE DATE(create_time) = '2026-01-01'、WHERE id + 1 = 5
  2. 隐式类型转换:varchar 列用数字查(WHERE phone = 13800000000)——MySQL 会做隐式 CAST,等同函数
  3. LIKE 以 % 开头:'%abc' 不走,'abc%' 走
  4. 违反最左前缀:跳过联合索引首列
  5. OR 连接非索引列:WHERE name = 'a' OR age > 20(age 无索引 → 全表扫;改为 UNION 或两边都建索引)
  6. 不等于/IS NOT NULL/NOT IN:!= 和 <> 通常不走索引(优化器评估后可能走)
  7. 字符集不一致:join 两表字段 collation 不同,隐式转换
  8. 优化器选择全表扫:数据量小/区分度低时,优化器认为全表更快(可 FORCE INDEX 但慎用)

🎯 面试要点

  • 排查手段:EXPLAIN 看 type(const > eq_ref > ref > range > index > ALL)和 key
  • 字符集统一 utf8mb4;字段上尽量避免函数(要优化就建函数索引 MySQL 8.0)
  • 区分度高的意思:该列不同值的数量/总行数,接近 1 最好

5. 索引的优缺点?什么时候不该建索引?

优点:加速查询(等值、范围、排序、覆盖)

代价:

不该建索引的场景:数据量小的表;区分度低的列(性别/状态);频繁更新的列;查询基本不用的列;大文本字段(可前缀索引)。

🎯 面试要点

  • 索引不是越多越好:一张表建议 ≤ 5 个左右,写入频繁的线上表更要克制
  • 前缀索引:INDEX(name(10)) 用前 10 字符做索引,省空间,但无法覆盖排序

🎤 常见面试追问

  1. 为什么不用红黑树做索引?——红黑树是二叉树,数据量大时树高几十层,每层一次磁盘 IO 太慢;B+ 树多路(一个节点存上千个键)三层就能存千万行。
  2. 什么是回表?怎么避免?——用二级索引查到主键后,还要回聚簇索引取整行。避免:覆盖索引(查询列全在索引里,含主键)。
  3. 为什么主键推荐自增整型?——自增有序:新记录追加到叶子末尾,页分裂少;UUID 无序导致频繁页分裂、索引膨胀、随机 IO。
  4. 联合索引 (a,b,c) 查 a 和 c 会怎样?——a 能走索引,c 用不上(a 相同的区间内 c 无序,违反最左前缀)。这就是"范围列之后的列失效"。
  5. 索引是越多越好吗?——不是:每个索引一棵 B+ 树,占磁盘;写入要维护所有索引(变慢);冗余索引浪费。一张表建议 ≤5 个。

📖 名词解释(本页术语)

术语 大白话解释
索引书的目录:帮你快速定位数据,不用整表扫。代价:占空间、写变慢。底层是 B+ 树。
B+ 树多路平衡树:只有叶子存数据(且叶子间链表相连),非叶子只存键。树矮(3 层千万行)、范围查询快。
聚簇索引主键索引:叶子直接存整行数据,表数据就是按主键排的。每个表只有一个。
二级索引(非聚簇)普通索引:叶子存"索引列值 + 主键",查完整数据要回表。
回表二级索引查到主键后,再回聚簇索引取整行的过程(多一次 IO)。
覆盖索引查询需要的列都在索引里(含主键),不用回表——查询优化利器。
最左前缀原则联合索引 (a,b,c) 查询必须从最左列 a 开始连续匹配才走索引(a、a+b、a+b+c 可以;b、c 不行)。
索引失效索引没被用上(全表扫)的情况:对列做函数/运算、隐式类型转换、LIKE '%x'、违反最左前缀等。
索引下推(ICP)联合索引中范围列之后的条件下推到引擎层过滤,减少回表(MySQL 5.6+ 默认开)。
页分裂B+ 树节点满了要拆成两个——无序插入(如 UUID)会频繁触发,性能差。
⚠️ 本页面由 AI 生成,内容仅供参考,请以官方文档和实际源码为准。