Skip to content

MySQL 8.0 面试题精选 ​

生成时间:2026-06-08 00:23 | 共 10 题


1. InnoDB 的内存架构包含哪几大核心组件?Buffer Pool 的淘汰策略是什么? ​

难度: ⭐⭐

标准答案:

InnoDB 内存架构核心组件:

  • Buffer Pool:缓存数据页和索引页,默认占物理内存的 80%
  • Change Buffer:缓存非唯一二级索引的 DML 操作(INSERT/UPDATE/DELETE),在空闲时 merge 到二级索引
  • Log Buffer:缓存 redo log,定期刷入 redo log file
  • Adaptive Hash Index:自动对频繁访问的索引页构建哈希索引

Buffer Pool 淘汰策略:改进的 LRU 算法,将 LRU 链表分为 young 区(热数据,5/8) 和 old 区(冷数据,3/8)。新读取的页先放入 old 区头部,如果在 innodb_old_blocks_time(默认1秒)内再次被访问,才移到 young 区头部。这有效避免了全表扫描等一次性读取冲刷掉热数据。

延伸思考: 为什么 Change Buffer 只针对非唯一二级索引?因为唯一索引在插入时必须立即检查唯一性约束,无法延迟合并。如果业务大量写非唯一二级索引,Change Buffer 可显著减少随机 I/O。


2. 什么是回表?如何利用覆盖索引避免回表? ​

难度: ⭐⭐

标准答案:

回表:当通过二级索引查到主键值后,还需要回到主键索引(聚簇索引)查找完整行数据的过程。回表会产生随机 I/O,是性能杀手。

覆盖索引:如果查询所需的所有列都包含在索引中,就可以直接从索引中获取数据,无需回表。执行计划的 Extra 列会显示 Using index。

示例:

sql
-- 有索引 idx_name_age(name, age)
-- 需要回表
SELECT name, age, email FROM user WHERE name = '张三';
-- 覆盖索引,不回表
SELECT name, age FROM user WHERE name = '张三';

优化建议:

  1. 尽量使查询字段被索引覆盖
  2. 避免使用 SELECT *
  3. 合理设计联合索引的列顺序(最左前缀原则)

延伸思考: 覆盖索引虽好,但索引列越多,索引占空间越大,写入性能也下降。需要在查询性能和写入成本之间做取舍。


3. MySQL 的四种事务隔离级别分别解决了什么问题?MySQL 8.0 默认是哪个级别? ​

难度: ⭐⭐

标准答案:

隔离级别脏读不可重复读幻读实现方式
READ UNCOMMITTED❌❌❌无锁无版本
READ COMMITTED✅❌❌MVCC(每次读生成新 Read View)
REPEATABLE READ✅✅大部分✅MVCC(首次读生成 Read View)
SERIALIZABLE✅✅✅所有 SELECT 加共享锁

MySQL 8.0 默认 REPEATABLE READ。

在 RR 级别下,InnoDB 通过 MVCC 解决了大部分幻读问题,但在当前读(SELECT ... FOR UPDATE / INSERT / UPDATE / DELETE)场景下仍可能出现幻读,此时需要通过 Gap Lock / Next-Key Lock 来防止幻读。

延伸思考: RC 级别下间隙锁基本失效(只用于外键约束和唯一性检查),这意味着在 RC 下无法通过间隙锁防幻读。实际生产中很多互联网公司选择 RC + 乐观锁方案,为什么?


4. 详细解释 InnoDB 的 MVCC 机制,包括隐藏字段、Undo Log 和 Read View 的工作原理。 ​

难度: ⭐⭐⭐

标准答案:

MVCC(Multi-Version Concurrency Control)是 InnoDB 实现非锁定读的核心机制。

隐藏字段(每行记录):

  • DB_TRX_ID:最近修改该行的事务 ID
  • DB_ROLL_PTR:回滚指针,指向 Undo Log 中该行的上一个版本
  • DB_ROW_ID:隐藏自增 ID(无主键时使用)

Undo Log 版本链:每次 UPDATE 操作,旧版本通过 DB_ROLL_PTR 串成链表,形成版本链。

Read View 可见性规则(核心):

  • m_ids:生成 Read View 时所有活跃事务 ID 列表
  • min_trx_id:活跃事务最小 ID
  • max_trx_id:下一个将分配的事务 ID
  • creator_trx_id:创建该 Read View 的事务 ID

判断某版本是否可见:

  1. DB_TRX_ID == creator_trx_id → 可见(自己的修改)
  2. DB_TRX_ID < min_trx_id → 可见(事务已提交)
  3. DB_TRX_ID >= max_trx_id → 不可见(事务在 Read View 之后开始)
  4. min_trx_id <= DB_TRX_ID < max_trx_id → 若在 m_ids 中则不可见,否则可见

如果当前版本不可见,沿版本链向前查找直到找到可见版本。

RR vs RC 的区别:RR 在事务首次 SELECT 时生成 Read View 并复用;RC 在每次 SELECT 时都生成新的 Read View。

延伸思考: 长事务会导致 Undo Log 无法清理,不仅占用空间,还会导致查询需要遍历更长的版本链,性能急剧下降。如何监控和处理长事务?


5. InnoDB 的行锁、表锁、间隙锁和意向锁分别在什么场景下使用?它们之间有什么配合关系? ​

难度: ⭐⭐⭐

标准答案:

行锁(Record Lock):锁定索引记录。只有在通过索引条件检索数据时才使用行锁,否则退化为表锁。

表锁(Table Lock):锁定整张表。LOCK TABLES 显式加表锁,或 DDL 操作隐式加元数据锁(MDL)。

间隙锁(Gap Lock):锁定索引记录之间的间隙,防止其他事务在间隙中插入新记录。仅在 RR 级别下生效。Gap Lock 之间不互斥,与插入操作互斥。

Next-Key Lock:行锁 + 间隙锁的组合(左开右闭),锁定一条记录及其前面的间隙。这是 InnoDB 在 RR 级别下默认的行锁算法。

意向锁(Intention Lock):表级锁,表示事务打算对表中的行加行锁。分为意向共享锁(IS)和意向排他锁(IX)。意向锁之间互相兼容,但与表级 S/X 锁互斥。

配合关系:

  • 加行锁前自动加意向锁(IS/IX)
  • 判断是否可以加表锁时,只需检查意向锁,无需逐行检查行锁
  • 意向锁是 InnoDB 自动管理的,无需手动干预

锁兼容矩阵:

ISIXSX
IS✅✅✅❌
IX✅✅❌❌
S✅❌✅❌
X❌❌❌❌

延伸思考: 死锁是行锁+间隙锁组合下的常见问题。如何通过 SHOW ENGINE INNODB STATUS 和 innodb_print_all_deadlocks 参数来分析和预防死锁?


6. 如何分析一条慢 SQL 并进行优化?请给出完整的优化流程。 ​

难度: ⭐⭐

标准答案:

优化流程:

  1. 开启慢查询日志

    sql
    SET GLOBAL slow_query_log = ON;
    SET GLOBAL long_query_time = 1;
    SET GLOBAL log_queries_not_using_indexes = ON;
  2. 定位慢 SQL

    bash
    mysqldumpslow -s t -t 10 /var/lib/mysql/slow.log
  3. EXPLAIN 分析执行计划

    • type 列:system > const > eq_ref > ref > range > index > ALL(避免 ALL)
    • key 列:实际使用的索引
    • rows 列:预估扫描行数
    • Extra 列:Using filesort / Using temporary / Using index 等
  4. 8.0 新增 EXPLAIN ANALYZE

    sql
    EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 100;

    返回实际执行时间,不只是估算。

  5. 优化手段

    • 添加合适索引(遵循最左前缀、避免索引失效场景)
    • 避免 SELECT *,利用覆盖索引
    • 避免在索引列上使用函数或隐式类型转换
    • 大分页优化:WHERE id > last_id LIMIT n 代替 OFFSET
    • 拆分复杂查询为简单查询
    • 使用 OPTIMIZER TRACE 分析优化器决策
      sql
      SET optimizer_trace = 'enabled=on';
      -- 执行查询后查看
      SELECT * FROM information_schema.OPTIMIZER_TRACE;

延伸思考: 有时优化器选择的索引不是最优的,如何通过 FORCE INDEX 或修改统计信息来引导优化器?8.0 的不可见索引(Invisible Index)如何帮助安全地验证索引效果?


7. MySQL 主从复制的原理是什么?有哪几种复制模式?各自优缺点? ​

难度: ⭐⭐

标准答案:

复制原理(三个线程):

  1. Binlog Dump Thread(主库):读取 binlog 发送给从库
  2. I/O Thread(从库):接收 binlog 写入 Relay Log
  3. SQL Thread(从库):读取 Relay Log 执行 SQL 语句

三种复制模式:

模式原理优点缺点
异步复制主库写完 binlog 即返回性能最好主库宕机可能丢数据
半同步复制至少一个从库确认收到 binlog 才返回兼顾性能和数据安全超时退化为异步
全同步复制所有从库执行完毕才返回数据最安全性能最差

MySQL 8.0 改进:

  • 增强半同步(After Sync):先写 binlog 到从库 Relay Log,再提交本地事务,避免主库宕机后数据丢失
  • WriteSet 复制:从库可并行回放无冲突的事务,大幅提升从库回放速度

延伸思考: 主从延迟是生产中的常见问题。如何通过并行复制(基于 DATABASE / WRITESET / LOCKS)、从库多线程回放来缓解?读写分离场景下如何处理读到旧数据的问题?


8. 什么时候需要分库分表?有哪些分片策略?MySQL 8.0 有哪些原生替代方案? ​

难度: ⭐⭐⭐

标准答案:

分库分表时机:

  • 单表数据量超过 2000 万行(B+ 树三层变四层)
  • 单库 QPS 超过 5000-10000
  • 磁盘 I/O 成为瓶颈

分片策略:

策略方式优点缺点
Hash 分片hash(shard_key) % N数据均匀扩容需迁移数据
Range 分片按 ID/时间范围扩容方便热点问题
一致性 Hash虚拟节点环扩容迁移少实现复杂
查找表映射表记录分片灵活映射表本身是瓶颈

MySQL 8.0 原生替代方案:

  1. 分区表(Partition):支持 RANGE, LIST, HASH, KEY 四种分区方式,8.0 改进了分区裁剪性能
  2. InnoDB Cluster:MySQL 官方高可用方案(MySQL Server + Group Replication + MySQL Router + MySQL Shell)
  3. InnoDB ReplicaSet:8.0 引入的轻量级复制管理方案

延伸思考: 分库分表后跨分片事务、跨分片 JOIN、全局唯一 ID、分布式排序等都是难题。是否可以考虑先用分区表 + 读写分离,延缓分库分表的时间?


9. MySQL 8.0 的窗口函数有哪些类型?请举例说明 ROW_NUMBER、RANK、LEAD/LAG 的用法。 ​

难度: ⭐⭐

标准答案:

窗口函数语法:

sql
函数名() OVER (
  [PARTITION BY 分组列]
  [ORDER BY 排序列 [ROWS|RANGE 窗口范围]]
)

常用窗口函数分类:

类型函数说明
排名ROW_NUMBER, RANK, DENSE_RANK区别在于并列排名的处理
偏移LEAD, LAG, FIRST_VALUE, LAST_VALUE访问前后行数据
聚合SUM, AVG, COUNT, MAX, MIN累计/滑动聚合
分布PERCENT_RANK, CUME_DIST, NTILE百分比/分组

示例:

sql
-- 1. 每个部门薪资排名(ROW_NUMBER 不并列,RANK 并列跳号,DENSE_RANK 并列不跳号)
SELECT name, dept, salary,
  ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS rn,
  RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS rnk,
  DENSE_RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS drnk
FROM employee;

-- 2. 环比增长率(LEAD/LAG)
SELECT month, revenue,
  LAG(revenue, 1) OVER (ORDER BY month) AS prev_month,
  (revenue - LAG(revenue, 1) OVER (ORDER BY month)) / 
   LAG(revenue, 1) OVER (ORDER BY month) AS growth_rate
FROM monthly_sales;

-- 3. 累计求和
SELECT month, revenue,
  SUM(revenue) OVER (ORDER BY month ROWS UNBOUNDED PRECEDING) AS cum_revenue
FROM monthly_sales;

延伸思考: 窗口函数 vs 子查询/自连接实现排名,性能差异如何?8.0 的窗口函数在执行计划中是如何优化的?


10. MySQL 8.0 对 JSON 的支持有哪些增强?如何为 JSON 列创建高效索引? ​

难度: ⭐⭐

标准答案:

8.0 JSON 增强功能:

  1. JSON 原生数据类型:二进制存储(不是字符串),支持局部更新,避免全量重写
  2. 丰富的 JSON 函数:
    • 查询:JSON_EXTRACT, ->, ->>, JSON_CONTAINS, JSON_SEARCH
    • 修改:JSON_SET, JSON_INSERT, JSON_REPLACE, JSON_REMOVE
    • 表函数:JSON_TABLE(8.0 新增,将 JSON 数组展开为关系表)
    • 聚合:JSON_ARRAYAGG, JSON_OBJECTAGG
    • 校验:JSON_SCHEMA_VALID, JSON_SCHEMA_VALIDATION_REPORT
  3. Partial Update:JSON_SET/JSON_REPLACE/JSON_REMOVE 在不改变 JSON 大小时原地更新,减少 binlog 和 I/O

JSON 索引方案:

sql
-- 方案1:生成列 + 普通索引
ALTER TABLE products ADD COLUMN category VARCHAR(50)
  GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(attrs, '$.category'))) STORED,
  ADD INDEX idx_category (category);

-- 方案2:MySQL 8.0.17+ 多值索引
ALTER TABLE products ADD INDEX idx_tags (
  (CAST(JSON_EXTRACT(attrs, '$.tags') AS CHAR(32) ARRAY))
);

-- 使用多值索引
SELECT * FROM products WHERE JSON_CONTAINS(attrs->'$.tags', '"电子"');
-- 或 MEMBER OF 语法
SELECT * FROM products WHERE '电子' MEMBER OF(attrs->'$.tags');

延伸思考: JSON 字段虽然灵活,但牺牲了关系型数据库的类型安全和约束检查能力。在设计时如何平衡灵活性与性能?什么场景下应该用 JSON 字段,什么场景下应该拆表?


📌 复习建议:重点掌握 MVCC 可见性判断逻辑、InnoDB 锁机制组合关系、EXPLAIN ANALYZE 实战用法,这三个是面试高频考点。