12-MySQL面试题汇总
总结
- 高频面试题覆盖:索引、事务、锁、日志、调优、主从复制
- 每道题都附有简明答案和详细链接
- 面试前重点复习:B+树、MVCC、二阶段提交、索引失效场景、慢查询优化
1. 索引相关
Q1:为什么 InnoDB 用 B+ 树而不是 B 树?
详细内容见 索引原理。
| 对比 | B 树 | B+ 树 |
|---|---|---|
| 数据存储 | 所有节点都存数据 | 只有叶子节点存数据 |
| 叶子节点 | 无链表 | 双向链表连接 |
| 范围查询 | 需要中序遍历 | 叶子节点顺序扫描 |
| 磁盘 IO | 相同数据量树更高 | 树更矮,IO 更少 |
答案:
- B+ 树非叶子节点不存数据,一个页能存更多索引项,树更矮,减少磁盘 IO
- 叶子节点用双向链表连接,范围查询直接顺序扫描,不用回溯
- 查询性能稳定,任何查询都要走到叶子节点
Q2:聚簇索引和非聚簇索引有什么区别?
详细内容见 聚簇索引 vs 普通索引。
答案:
- 聚簇索引:叶子节点就是数据本身,一张表只有一个(主键索引)
- 非聚簇索引:叶子节点存的是索引字段 + 主键值,需要回表查完整数据
Q3:什么是回表?怎么避免?
答案:用非聚簇索引查到主键后,还要去聚簇索引查完整数据,这就是回表。
避免方法:使用覆盖索引,让查询需要的字段都在索引里。
-- 联合索引 (name, age)
SELECT name, age FROM user WHERE name = '张三'; -- 覆盖索引,不回表
SELECT * FROM user WHERE name = '张三'; -- 需要回表
Q4:联合索引的最左前缀原则是什么?
详细内容见 联合索引匹配规则。
答案:联合索引 (a, b, c) 的查询规则:
WHERE a=1✓WHERE a=1 AND b=2✓WHERE a=1 AND b=2 AND c=3✓WHERE b=2✗(跳过了 a)WHERE a=1 AND c=3✓ 部分命中(只用到 a)
Q5:哪些情况下索引会失效?
详细内容见 索引失效场景。
高频场景:
- 对索引字段用函数:
WHERE YEAR(create_time) = 2024 - 隐式类型转换:
WHERE phone = 13800138000(phone 是 varchar) - LIKE 左模糊:
WHERE name LIKE '%三' - 联合索引跳过中间字段:
WHERE a=1 AND c=3(跳过 b) - OR 条件没全建索引:
WHERE a=1 OR b=2(只有 a 有索引)
2. 事务相关
Q6:事务的四大特性(ACID)是什么?
| 特性 | 说明 | 实现机制 |
|---|---|---|
| 原子性(Atomicity) | 事务要么全成功,要么全失败 | undo log |
| 一致性(Consistency) | 数据从一个一致状态到另一个一致状态 | 其他三个特性的共同保证 |
| 隔离性(Isolation) | 并发事务互不干扰 | 锁 + MVCC |
| 持久性(Durability) | 提交后数据永久保存 | redo log |
Q7:四种隔离级别分别解决什么问题?
详细内容见 事务隔离级别。
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | ✓ | ✓ | ✓ |
| READ COMMITTED | ✗ | ✓ | ✓ |
| REPEATABLE READ | ✗ | ✗ | ✓ |
| SERIALIZABLE | ✗ | ✗ | ✗ |
MySQL 默认 RR 级别,且能避免幻读(靠 MVCC + 间隙锁)。
Q8:MVCC 是怎么实现的?
详细内容见 MVCC 机制。
答案:两个核心组件:
- undo log 版本链:每行数据有隐藏字段
trx_id和roll_pointer,串成版本链 - ReadView:事务查询时生成快照,包含 m_ids、min_trx_id、max_trx_id、creator_trx_id
RC 每次查询重新生成 ReadView,RR 事务内只生成一次。
Q9:redo log 和 binlog 有什么区别?
详细内容见 日志系统。
| 对比 | redo log | binlog |
|---|---|---|
| 所属层 | InnoDB | Server |
| 日志类型 | 物理日志 | 逻辑日志 |
| 用途 | 崩溃恢复 | 主从复制、数据恢复 |
| 写入方式 | 循环写 | 追加写 |
Q10:什么是二阶段提交?为什么需要?
详细内容见 二阶段提交。
答案:事务提交分两步:
- prepare:写 redo log,标记为 prepare 状态
- commit:写 binlog,然后把 redo log 标记为 commit
为什么需要:保证 redo log 和 binlog 一致。崩溃恢复时:
- prepare + binlog 存在 → 提交
- prepare + binlog 不存在 → 回滚
3. 锁相关
Q11:InnoDB 有哪些锁?
详细内容见 锁机制详解。
按粒度分:全局锁、表锁、行锁
按模式分:共享锁(S)、排他锁(X)、意向锁(IS/IX)
按算法分:记录锁、间隙锁、临键锁
Q12:什么是间隙锁?有什么用?
答案:锁的是索引记录之间的间隙,防止其他事务在间隙里插入数据,用于解决 RR 级别的幻读。
-- 假设 id 有 10, 20, 30
SELECT * FROM user WHERE id > 15 AND id < 25 FOR UPDATE;
-- 锁住间隙 (10, 20) 和 (20, 30)
Q13:怎么避免死锁?
答案:
- 按相同顺序访问表和行
- 大事务拆成小事务
- 确保 WHERE 条件用上索引,避免锁升级为表锁
- 设置合理的锁等待超时时间
4. 调优相关
Q14:慢查询怎么排查?
步骤:
- 开启慢查询日志:
slow_query_log = ON - 找到慢 SQL:
mysqldumpslow或直接看日志 EXPLAIN分析执行计划- 看 type(有没有 ALL)、key(有没有走索引)、Extra(有没有 filesort)
- 针对性优化:加索引、改写 SQL、调整表结构
Q15:Buffer Pool 怎么配置?
详细内容见 Buffer Pool 配置。
答案:
- 大小:机器内存的 50%~60%
- 实例数:每个实例约 1G,别超过 64 个
- 配置约束:
size必须是chunk_size × instances的整数倍 - 建议:
innodb_flush_log_at_trx_commit=1+sync_binlog=1
Q16:深分页怎么优化?
详细内容见 评论系统深分页。
答案:子查询先查 ID 再回表
-- 慢:回表 10 万次
SELECT * FROM comments WHERE product_id='xxx' LIMIT 100000, 20;
-- 快:子查询只查 ID(覆盖索引),再回表 20 次
SELECT * FROM comments a,
(SELECT id FROM comments WHERE product_id='xxx' LIMIT 100000, 20) b
WHERE a.id = b.id;
5. 架构相关
Q17:主从复制原理是什么?
详细内容见 主从复制。
答案:基于 binlog 的三个线程:
- 主库 binlog dump 线程:读取 binlog 发送给从库
- 从库 IO 线程:拉取 binlog 写入 relay log
- 从库 SQL 线程:回放 relay log
Q18:主从延迟怎么处理?
答案:
- 关键读走主库
- 判断
Seconds_Behind_Master - 写完后 1-2 秒内的读走主库
- 用 GTID 检查从库是否已回放到指定位置
Q19:读写分离怎么做?
答案:
- 应用层代码判断 SQL 类型路由
- 用 ShardingSphere、MyCat 等中间件
- 用 ProxySQL 做透明代理
6. 其他高频题
Q20:char 和 varchar 的区别?
| 对比 | char | varchar |
|---|---|---|
| 长度 | 固定长度 | 可变长度 |
| 存储 | 不足补空格 | 只存实际数据 |
| 性能 | 定长,更快 | 变长,需要记录长度 |
| 适用场景 | 长度固定的字段(如手机号) | 长度变化大的字段 |
Q21:count(*)、count(1)、count(列名) 有什么区别?
| 写法 | 说明 |
|---|---|
COUNT(*) |
统计所有行,包括 NULL |
COUNT(1) |
统计所有行,和 COUNT(*) 效果一样 |
COUNT(列名) |
只统计该列非 NULL 的行 |
InnoDB 下 COUNT(*) 和 COUNT(1) 性能差不多,优化器会自动选择最优方式。
Q22:为什么建议用自增主键?
详细内容见 页分裂。
答案:
- 自增主键总是最大的,插入时追加到最后一个页,不用分裂
- 整型比 UUID 小(4/8 字节 vs 36 字节),索引树更矮
- 顺序存储,范围查询性能好
Q23:truncate 和 delete 的区别?
| 对比 | DELETE | TRUNCATE |
|---|---|---|
| 类型 | DML | DDL |
| 删除方式 | 逐行删除 | 直接释放数据页 |
| 日志 | 记录每行删除 | 只记录页释放 |
| 速度 | 慢(大表) | 快 |
| 回滚 | 可以回滚 | 不能回滚 |
| 自增 ID | 不重置 | 重置为初始值 |