MYSQL—DAY1

为什么 InnoDB 使用 B+ 树作为索引,而不是 B 树、红黑树或 Hash?

InnoDB 使用 B+ 树,主要是因为数据库索引需要持久化到磁盘中,而磁盘随机 I/O 的成本很高,所以索引结构需要尽可能降低树的高度和磁盘页访问次数。

B+ 树是一种多叉平衡树,一个磁盘页可以存储大量索引键和子节点指针,因此分支数量多,树通常只有 3~4 层,查询时需要访问的磁盘页较少。

B 树的非叶子节点也存储数据,在固定磁盘页大小下,每个节点能容纳的索引键更少,导致分叉数更少 ,因此相同数据量下树高通常会高于 B+ 树。B+ 树非叶子节点只存索引键和子节点指针,扇出更大,可以减少磁盘 I/O。同时,所有记录都位于叶子节点,叶子节点按索引值有序排列并通过链表连接,非常适合范围查询、排序和顺序扫描。

红黑树属于二叉树,节点分支少,数据量较大时树高较高,如果用于磁盘索引,会产生更多磁盘页访问。

Hash 索引虽然适合等值查询,但数据是无序的,不支持高效的范围查询、排序和联合索引的最左前缀匹配。

跳表更适合内存场景,例如 Redis。它会产生较多指针跳转和随机内存访问,但 Redis 数据主要存储在内存中,因此不存在普通磁盘索引所关注的大量随机磁盘 I/O 问题。如果跳表用于磁盘索引,分散的节点访问反而可能导致较多随机 I/O。

因此,综合磁盘 I/O、树高、范围查询和排序等需求,InnoDB 选择 B+ 树作为主要索引结构。

什么是聚簇索引、二级索引、回表和覆盖索引?

假设表是:

sql 复制代码
CREATE TABLE user (
    id INT PRIMARY KEY,
    name VARCHAR(20),
    age INT,
    INDEX idx_age(age)
);

聚簇索引的叶子节点存什么?

主键索引 id 是聚簇索引,它的叶子节点存放的是:id + name + age

二级索引的叶子节点存什么?

idx_age(age) 是二级索引,它的叶子节点存放的是:age + 主键 id

聚簇索引是什么?

聚簇索引就是叶子节点直接存放完整一行数据的索引。在 InnoDB 中,主键索引通常就是聚簇索引,所以通过主键查询时,找到索引就能直接拿到完整数据。

二级索引是什么?

二级索引就是除聚簇索引之外的普通索引。它的叶子节点不存完整数据,只存"索引字段和主键值"。

回表是什么?

使用二级索引查询时,先通过二级索引找到主键,再根据主键去聚簇索引中查询完整数据,这个再次查询的过程就叫回表。

覆盖索引是什么?

如果查询需要的字段在二级索引中已经全部存在,就可以直接返回结果,不需要再去聚簇索引中查询,这就叫覆盖索引。

例如:CREATE INDEX idx_age_name ON user(age, name);

SELECT name FROM user WHERE age = 20; 不需要回表,是覆盖索引

什么是联合索引的最左前缀原则?为什么 (A,B,C) 索引下,WHERE B=? AND A=? AND C=? 仍然可以走索引,而 A=? OR B=? 可能不行?

联合索引 (A,B,C) 是先按 A 排序,A 相同时按 B 排序,A、B 都相同时再按 C 排序,所以索引使用一般要从最左列 A 开始

最左前缀原则与 WHERE 条件的书写顺序无关,因此 B=? AND A=? AND C=? 中同时存在 A、B、C,优化器可以按照索引顺序使用完整联合索引。

A=? OR B=? 中,AND 中可以先确定 A,再查 B;OR 中满足 B 的数据不一定满足 A,所以 B 这一支缺少最左列 A,可能无法使用 (A,B,C) 联合索引。

查询条件 索引使用情况
A = ? 可以使用 A
A = ? AND B = ? 可以使用 A、B
A = ? AND B = ? AND C = ? 可以使用 A、B、C
B = ? AND A = ? 可以使用 A、B,条件顺序无影响
B = ? 通常不能有效使用联合索引定位
A = ? AND C = ? A 可用于定位,C 通常不能连续用于定位
A > ? AND B = ? A 用于范围定位,B 通常不能继续缩小索引扫描范围
A = ? OR B = ? 可能无法使用该联合索引,或使用索引合并

哪些情况可能导致索引无法高效使用?

重点包括:前置模糊查询、对索引列使用函数或计算、隐式类型转换、联合索引缺少最左列、范围查询影响后续索引列。

  1. 联合索引不满足最左前缀原则;

  2. 对索引列使用函数或计算;

  3. 索引字段发生隐式类型转换;

  4. 关联字段的字符集或排序规则不同,导致隐式转换;

  5. .LIKE 查询以 % 开头;

  6. OR的混用

  7. 使用 !=NOT INNOT LIKE 等否定条件;

  8. 查询返回的数据量过大或回表次数过多,优化器认为全表扫描成本更低。

不满足最左前缀原则

联合索引:

sql 复制代码
INDEX idx_abc(A, B, C)
sql 复制代码
WHERE B = 2;

缺少最左列 A,通常不能高效使用联合索引。

联合索引中间断开

sql 复制代码
WHERE A = 1 AND C = 3;

可以使用 A,但跳过了 BC 通常不能继续用于索引定位。


对索引列使用函数或计算

sql 复制代码
WHERE YEAR(create_time) = 2026;
WHERE age + 1 = 20;

可以改成:

sql 复制代码
WHERE create_time >= '2026-01-01'
  AND create_time < '2027-01-01';

WHERE age = 19;

发生隐式类型转换

phoneVARCHAR

sql 复制代码
WHERE phone = 13800138000;

应写成:

sql 复制代码
WHERE phone = '13800138000';

否则可能对索引列进行类型转换。


LIKE 以 % 开头

sql 复制代码
WHERE name LIKE '%张%';

通常不能高效使用普通 B+ 树索引。

sql 复制代码
WHERE name LIKE '张%';

通常可以使用索引。


OR 中一边没有可用索引

联合索引是 (A,B,C)

sql 复制代码
WHERE A = 1 OR B = 2;

A=1 可以走索引,OR 中满足 B 的数据不一定满足 A,所以 B 这一支缺少最左列 A,可能无法使用 (A,B,C) 联合索引,所以整体可能全表扫描。


使用否定条件

sql 复制代码
WHERE age != 20;
WHERE id NOT IN (1, 2, 3);
WHERE name NOT LIKE '张%';

查询结果太多或回表太多

sql 复制代码
SELECT *
FROM user
WHERE gender = 1;

如果全表大部分数据都是 gender=1,走索引需要大量回表,MySQL 可能直接全表扫描。

如何使用 EXPLAIN 判断 SQL 是否走索引?

重点掌握:typepossible_keyskeykey_lenrowsExtra,以及 Using indexUsing index condition 的区别。

使用 EXPLAIN 查看 SQL 执行计划,主要看这几个字段:

  1. possible_keys :可能使用的索引;为 NULL 表示没有合适的索引。

  2. key :实际使用的索引;key 不为 NULL,说明走了索引。

  3. type:查询方式,常见性能由好到差为:

    const > eq_ref > ref > range > index > ALL

其中 ALL 是全表扫描,index 是全索引扫描。

  1. rows:预计扫描的行数,通常越少越好。
  2. Extra :额外信息,Using index 表示覆盖索引;Using filesort 表示额外排序;Using temporary 表示使用临时表。

一句话记忆:key 判断有没有走索引,看 typerowsExtra 判断索引走得好不好。

重点看这些字段:

  1. possible_keys = idx_tap_user_classname
    表示优化器认为 classname 上的索引可以使用。
  2. key = idx_tap_user_classname
    表示最终确实选择了这个索引。判断是否走索引,主要看 key 是否为 NULL
  3. type = ref
    表示通过普通非唯一索引进行等值查询,属于比较好的访问方式。
  4. ref = const
    表示拿一个固定值:计科25转专业同学C语言去索引中查找。
  5. rows = 1
    优化器预计只需要扫描一条记录,说明这个条件的区分度较好。
  6. filtered = 100%
    表示扫描到的记录预计全部符合条件,不需要再过滤大量数据。
  7. Extra = NULL
    没有出现 Using filesortUsing temporary 等额外操作。
相关推荐
kyriewen15 小时前
面试官让我手写虚拟列表——AI生成的版本,快速滚动几下就白屏了
前端·javascript·面试
Revolution6116 小时前
React 组件重新渲染时,到底重新执行了什么
前端·react.js·面试
Revolution6118 小时前
第一次运行 Node.js:终端里的 JavaScript 怎样执行
后端·面试·node.js
进击的程序猿~18 小时前
Go 并发底层原理面试学习指南
开发语言·面试·golang
wdfk_prog19 小时前
嵌入式面试真题学习笔记系列
笔记·学习·面试
用户2873555325921 天前
OpenResty 中 Lua 脚本实战示例(分场景讲清 Lua 解决了 Nginx 原生做不到的事)
面试
帅次1 天前
Android 应用高级面试:Window 近1年高频追问 18 题
android·面试·职场和发展
Revolution611 天前
Node.js 是什么:前端项目里哪些事情由它完成
前端·面试·node.js